Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ldap form based login #175

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ Installation

[Full nginx server example](https://github.com/operasoftware/dns-ui/wiki/Example-configuration:-nginx)

5. Set up an authentication module for your virtual host (eg. authnz_ldap for Apache).
5. Set up authentication

* Either using the old-style way using an authentication module for your virtual host (eg. authnz_ldap for Apache).

* Or using HTML form-based authentication using LDAP by setting form_based = "ldap" in config.ini and enabling and configuring LDAP there as well.

6. Copy the file `config/config-sample.ini` to `config/config.ini` and edit the settings as required.

Expand Down
127 changes: 127 additions & 0 deletions auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php
##
## Copyright 2013-2018 Opera Software AS
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##

function is_form_authenticated() {
global $config;
if ($config['authentication']['form_based'] == "ldap") {
if (isset($_SESSION['loggedin']) && $_SESSION['loggedin']) {
return true;
}
}
return false;
}

function dns_ui_start_session() {
global $config;

$options = array();
$options['use_strict_mode'] = true;

if (isset($config['session'])) {
// allow to set some session options from configuration
$whitelisted = array('name', 'cookie_path', 'cookie_lifetime', 'cookie_secure');

foreach($config['session'] as $k => $v) {
if (array_search($k, $whitelisted) !== FALSE) {
$options[$k] = $v;
}
}
}
session_start($options);
}

function auth_by_ldap($user, $pass) {
global $config;
global $ldap;

if ( ! $config['ldap']['enabled']) {
error_log("Use of LDAP must be enabled to use LDAP form-based authentication");
throw new Exception('Misconfiguration detected - check the error log');
}

return $ldap->auth($user, $pass,
$config['ldap']['user_id'], $config['ldap']['dn_user'],
isset($config['ldap']['extra_user_filter'])
? $config['ldap']['extra_user_filter']
: null
);
}

if ($config['authentication']['form_based'] !== false) {
dns_ui_start_session();
if (! isset($_SESSION['loggedin']) || ! $_SESSION['loggedin']) {
$_SESSION['loggedin'] = false;

if (!empty($_POST) && $relative_request_url == '/login' ) {
if (isset($_POST['username']) && isset($_POST['password'])) {
$authed = false;

try {
// other authentication methods could be implemented here...
if ($config['authentication']['form_based'] == "ldap") {
$authed = auth_by_ldap($_POST['username'], $_POST['password']);
}

if ($authed) {
// OK, authenticated - but can we get user details???
// if we can't this will throw an exception...
$active_user = $user_dir->get_user_by_uid($_POST['username']);

if(!$active_user->active) {
// user is no longer active. Behave as if login failed
error_log("Login attempt by inactive user '" . $_POST['username'] . "'");
} else {
$_SESSION['loggedin'] = true;
$_SESSION['user'] = $_POST['username'];
require('views/home.php');
die;
}
} else {
error_log("Failed login attempt for user '" . $_POST['username'] . "'");
}
} catch (Exception $e) {
$_SESSION['loggedin'] = false;
$_SESSION['user'] = null;

error_log($e);
$alert = new UserAlert;
$alert->content = sprintf('Login failed: %s', $e->getMessage());
$login_alerts = array($alert);

require('views/login.php');
die;
}
}
}

if (! $_SESSION['loggedin']) {
require('views/login.php');
die;
}
}

if (isset($_SESSION['loggedin']) && $_SESSION['loggedin']) {
if ($relative_request_url == '/logout' ) {
$_SESSION['loggedin'] = false;
$_SESSION['user'] = null;
require('views/home.php');
die;
}

$active_user = $user_dir->get_user_by_uid($_SESSION['user']);
}
}
51 changes: 51 additions & 0 deletions config/config-sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,38 @@ password = password
; compare the user ID's case? (on by default)
user_case_sensitive = 1

; Set this to "ldap" to enable HTML form-based login to dns-ui. Do not forget
; to complete LDAP configuration in the [ldap] section below. Also make sure
; that php_auth is NOT enabled at the same time.
; If you enable this you usually MUST NOT configure authentication on the
; webserver itself.
; form_based = "ldap"
form_based = false

[session]
; If form based authentication is enabled, the underlying cookie can be configured
; through this section.
;
; NOTE: if you are running multiple instances of the dns-ui on the same hostname
; under different paths, then consider to either change the cookie name for
; at least one instance and/or change the cookie_path for both instances
;
; the cookie name.
name = DNSUI

; if you are running dns-ui under some path on your server then
; consider to specify this path here as well in order to avoid
; leaking the cookie to other applications
; cookie_path = /

; the lifetime of the cookie in seconds. This causes "auto logout"
; after some time
cookie_lifetime = 10400

; if you are using HTTPS (you should) then consider to set this to true to
; avoid leaking the cookie over HTTP
; cookie_secure = true

[php_auth]
enabled = 0
admin_group = "systems"
Expand Down Expand Up @@ -89,6 +121,25 @@ group_member_value = uid
; Members of admin_group are given full access to DNS UI web interface
admin_group_cn = administrators

;
; An additional filter that is used when looking for an LDAP user. This is ANDed with
; the filter used to lookup the user id. This is very convenient if you have an LDAP
; than supports eg. the memberOf attribute to filter for specific groups.
; The idea is also to have a waterproof filter to avoid any possibility of allowing
; invalid users to authenticate through the form based login only to later get some
; access denied message, indirectly leaking user information or even allowing for a
; brute force authentication attack.
;
; Note that if a user is NOT found using this filter but exists in the local database
; it will be considered to be no longer active.
;
; Example: To make sure that only members of the dns-ui-admin or dns-ui-users groups can
; ever login one may use something like
;
; extra_user_filter = "(|(memberOf=cn=dns-ui-admins,ou=IT,o=ORG)(memberOf=cn=dns-ui-users,ou=IT,o=ORG))"
;
; extra_user_filter =

[powerdns]
api_url = "http://localhost:8081/api/v1/servers/localhost"
api_key = api_key
Expand Down
42 changes: 42 additions & 0 deletions ldap.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,48 @@ private function connect() {
}
}

public function auth($uid, $pass, $user_id_attr, $basedn, $extrafilter) {
if(is_null($this->conn)) $this->connect();

$filter = sprintf("(%s=%s)", LDAP::escape($user_id_attr), LDAP::escape($uid));
if ( isset($extrafilter) ) {
$filter = sprintf("(&%s%s)", $extrafilter, $filter);
}

$r = @ldap_search($this->conn, $basedn, $filter);

if(! $r) {
return false;
}

// Fetch entries
$result = @ldap_get_entries($this->conn, $r);

if ($result['count'] != 1) {
return false;
}

$authdn = $result[0]['dn'];

$authconn = ldap_connect($this->host);
if($authconn === false) throw new LDAPConnectionFailureException('Invalid LDAP connection settings');
if($this->starttls) {
if(!ldap_start_tls($authconn)) throw new LDAPConnectionFailureException('Could not initiate TLS connection to LDAP server');
}
foreach($this->options as $option => $value) {
ldap_set_option($authconn, $option, $value);
}

try {
$bound = @ldap_bind($authconn, $authdn, $pass);
return $bound;
} catch (Exception $e) {
return false;
} finally {
@ldap_unbind($authconn);
}
}

public function search($basedn, $filter, $fields = array(), $sort = array()) {
if(is_null($this->conn)) $this->connect();
if(empty($fields)) $r = @ldap_search($this->conn, $basedn, $filter);
Expand Down
14 changes: 13 additions & 1 deletion model/user.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,21 @@ public function get_details_from_ldap() {
if(isset($config['ldap']['user_active'])) {
$attributes[] = $config['ldap']['user_active'];
}
$ldapusers = $this->ldap->search($config['ldap']['dn_user'], LDAP::escape($config['ldap']['user_id']).'='.LDAP::escape($this->uid), array_keys(array_flip($attributes)));

$filter = sprintf("(%s=%s)", LDAP::escape($config['ldap']['user_id']), LDAP::escape($this->uid));
if ( isset($config['ldap']['extra_user_filter']) ) {
$filter = sprintf("(&%s%s)", $config['ldap']['extra_user_filter'], $filter);
}

$ldapusers = $this->ldap->search($config['ldap']['dn_user'], $filter, array_keys(array_flip($attributes)));
if($ldapuser = reset($ldapusers)) {
$this->auth_realm = 'LDAP';

foreach (array('user_id', 'user_name', 'user_email') as $key) {
if (!isset($ldapuser[strtolower($config['ldap'][$key])])) {
throw new UserNotFoundException(sprintf('User misses %s attribute in LDAP directory.', $config['ldap'][$key]));
}
}
$this->uid = $ldapuser[strtolower($config['ldap']['user_id'])];
$this->name = $ldapuser[strtolower($config['ldap']['user_name'])];
$this->email = $ldapuser[strtolower($config['ldap']['user_email'])];
Expand Down
26 changes: 19 additions & 7 deletions pagesection.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,25 @@ public function __construct($template) {
$this->template = $template;
$this->data = new StdClass;
$this->data->menu_items = array();
$this->data->menu_items['Zones'] = '/zones';
if(is_object($active_user) && $active_user->admin) {
$this->data->menu_items['Templates'] = array();
$this->data->menu_items['Templates']['SOA templates'] = '/templates/soa';
$this->data->menu_items['Templates']['Nameserver templates'] = '/templates/ns';
$this->data->menu_items['Users'] = '/users';
$this->data->menu_items['Settings'] = '/settings';

$add_menu_items = true;
if ($config['authentication']['form_based']) {
/* Do NOT add any menu items if we have not been authenticated */
$add_menu_items = is_form_authenticated();
}

if ($add_menu_items) {
$this->data->menu_items['Zones'] = '/zones';
if(is_object($active_user) && $active_user->admin) {
$this->data->menu_items['Templates'] = array();
$this->data->menu_items['Templates']['SOA templates'] = '/templates/soa';
$this->data->menu_items['Templates']['Nameserver templates'] = '/templates/ns';
$this->data->menu_items['Users'] = '/users';
$this->data->menu_items['Settings'] = '/settings';
}
if ($config['authentication']['form_based']) {
$this->data->menu_items['Log out'] = '/logout';
}
}
$this->data->relative_request_url = $relative_request_url;
$this->data->active_user = $active_user;
Expand Down
4 changes: 4 additions & 0 deletions public_html/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ div.stickyHeader th {
background-color: white;
}

input.authbox {
width: auto;
}

/**
* GeSHi (C) 2004 - 2007 Nigel McNie, 2007 - 2008 Benny Baumann
* (http://qbnz.com/highlighter/ and http://geshi.org/)
Expand Down
16 changes: 10 additions & 6 deletions requesthandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@
ob_start();
set_exception_handler('exception_handler');

if(isset($_SERVER['PHP_AUTH_USER'])) {
$active_user = $user_dir->get_user_by_uid($_SERVER['PHP_AUTH_USER']);
} else {
throw new Exception("Not logged in.");
}

// Work out where we are on the server
$request_url = preg_replace('|(.)/$|', '$1', $_SERVER['REQUEST_URI']);
$relative_request_url = preg_replace('/^'.preg_quote($relative_frontend_base_url, '/').'/', '', $request_url) ?: '/';
$absolute_request_url = $frontend_root_url.$request_url;

if ($config['authentication']['form_based']) {
require('auth.php');
} else {
if(isset($_SERVER['PHP_AUTH_USER'])) {
$active_user = $user_dir->get_user_by_uid($_SERVER['PHP_AUTH_USER']);
} else {
throw new Exception("Not logged in.");
}
}

if(empty($config['web']['enabled'])) {
require('views/error503.php');
die;
Expand Down
41 changes: 41 additions & 0 deletions templates/login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
##
## Copyright 2013-2018 Opera Software AS
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
## http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
?>
<form method="post" action="<?php outurl('/login')?>" class="form-horizontal">
<fieldset>
<legend>Login</legend>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">User</label>
<div class="col-sm-10">
<input type="text" class="form-control authbox" id="username" name="username" required pattern="\S+" maxlength="255" value="">
</div>
</div>

<div class="form-group">
<label for="name" class="col-sm-2 control-label">Password</label>
<div class="col-sm-10">
<input type="password" class="form-control authbox" id="password" name="password" required pattern="\S+" maxlength="255" value="">
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary" name="update_settings" value="1">Login</button>
</div>
</div>
</fieldset>
</form>
Loading