diff --git a/README.md b/README.md index 6524b6c..923947d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/auth.php b/auth.php new file mode 100644 index 0000000..33f7f12 --- /dev/null +++ b/auth.php @@ -0,0 +1,127 @@ + $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']); + } +} diff --git a/config/config-sample.ini b/config/config-sample.ini index ec9b5df..adc6084 100644 --- a/config/config-sample.ini +++ b/config/config-sample.ini @@ -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" @@ -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 diff --git a/ldap.php b/ldap.php index 2b033ed..207bcf5 100644 --- a/ldap.php +++ b/ldap.php @@ -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); diff --git a/model/user.php b/model/user.php index d570e3b..87a6945 100644 --- a/model/user.php +++ b/model/user.php @@ -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'])]; diff --git a/pagesection.php b/pagesection.php index 31912df..9e539d6 100644 --- a/pagesection.php +++ b/pagesection.php @@ -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; diff --git a/public_html/style.css b/public_html/style.css index a559bc0..c948bdb 100644 --- a/public_html/style.css +++ b/public_html/style.css @@ -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/) diff --git a/requesthandler.php b/requesthandler.php index 26fca44..c75c3db 100644 --- a/requesthandler.php +++ b/requesthandler.php @@ -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; diff --git a/templates/login.php b/templates/login.php new file mode 100644 index 0000000..6b04ae6 --- /dev/null +++ b/templates/login.php @@ -0,0 +1,41 @@ + +
+
+ Login +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
diff --git a/views/login.php b/views/login.php new file mode 100644 index 0000000..9f9ed66 --- /dev/null +++ b/views/login.php @@ -0,0 +1,32 @@ +set('title', 'Login'); +$page->set('content', $content); + +$alerts = array(); +## Argh - what an ugly interface... +if (isset($login_alerts)) { + $alerts = $login_alerts; +} +$page->set('alerts', $alerts); + +echo $page->generate(); +