diff --git a/config-templates/authsources.php b/config-templates/authsources.php index 2fb5aec934e94d8c69d824ab608fa03fe8ac78cf..7ca97b9cd5464b3b16459212b72a10acc09ae0e5 100644 --- a/config-templates/authsources.php +++ b/config-templates/authsources.php @@ -62,6 +62,94 @@ $config = array( 'secret' => 'xxxxxxxxxxxxxxxx', ), + /* Example of a LDAP authentication source. */ + 'example-ldap' => array( + 'ldap:LDAP', + + /* The hostname of the LDAP server. */ + 'hostname' => 'ldap.example.org', + + /* Whether SSL/TLS should be used when contacting the LDAP server. */ + 'enable_tls' => FALSE, + + /* + * Which attributes should be retrieved from the LDAP server. + * This can be an array of attribute names, or NULL, in which case + * all attributes are fetched. + */ + 'attributes' => NULL, + + /* + * The pattern which should be used to create the users DN given the username. + * %username% in this pattern will be replaced with the users username. + * + * This option is not used if the search.enable option is set to TRUE. + */ + 'dnpattern' => 'uid=%username%,ou=people,dc=example,dc=org', + + /* + * As an alternative to specifying a pattern for the users DN, it is possible to + * search for the username in a set of attributes. This is enabled by this option. + */ + 'search.enable' => FALSE, + + /* + * The DN which will be used as a base for the search. + * This can be a single string, in which case only that DN is searched, or an + * array of strings, in which case they will be searched in the order given. + */ + 'search.base' => 'ou=people,dc=example,dc=org', + + /* + * The attribute(s) the username should match against. + * + * This is an array with one or more attribute names. Any of the attributes in + * the array may match the value the username. + */ + 'search.attributes' => array('uid', 'mail'), + + /* + * The username & password the simpleSAMLphp should bind to before searching. If + * this is left as NULL, no bind will be performed before searching. + */ + 'search.username' => NULL, + 'search.password' => NULL, + ), + + /* Example of an LDAPMulti authentication source. */ + 'example-ldapmulti' => array( + 'ldap:LDAPMulti', + + /* + * A list of available LDAP servers / user groups. The value of each element is + * an array in the same format as an LDAP authentication source. + */ + 'employees' => array( + /* + * A short name/description for this group. Will be shown in a dropdown list + * when the user logs on. + * + * This option can be a string or an array with language => text mappings. + */ + 'description' => 'Employees', + + /* + * The rest of the options are the same as those available for + * the LDAP authentication source. + */ + 'hostname' => 'ldap.employees.example.org', + 'dnpattern' => 'uid=%username%,ou=employees,dc=example,dc=org', + ), + + 'students' => array( + 'description' => 'Students', + + 'hostname' => 'ldap.students.example.org', + 'dnpattern' => 'uid=%username%,ou=students,dc=example,dc=org', + ), + + ), + ); ?> \ No newline at end of file diff --git a/modules/ldap/default-enable b/modules/ldap/default-enable new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/modules/ldap/docs/ldap.txt b/modules/ldap/docs/ldap.txt new file mode 100644 index 0000000000000000000000000000000000000000..9ed6e6f8d31e41ddfe210fac459fac1cc893b6a1 --- /dev/null +++ b/modules/ldap/docs/ldap.txt @@ -0,0 +1,170 @@ +LDAP module +=========== + +The LDAP module provides a method for authenticating users against a +LDAP server. There are two seperate authentication modules: + + +`ldap:LDAP` +: Authenticate the user against a single LDAP server. + +`ldap:LDAPMulti` +: Allow the user to chose one LDAP server to authenticate against. + + +`ldap:LDAP` +----------- + +This module is used when you have an organization with a single LDAP +server with all the users. To create a LDAP authentication source, open +`config/authsources.php` in a text editor, and add an entry for the +authentication source: + + 'example-ldap' => array( + 'ldap:LDAP', + + /* The hostname of the LDAP server. */ + 'hostname' => 'ldap.example.org', + + /* Whether SSL/TLS should be used when contacting the LDAP server. */ + 'enable_tls' => FALSE, + + /* + * Which attributes should be retrieved from the LDAP server. + * This can be an array of attribute names, or NULL, in which case + * all attributes are fetched. + */ + 'attributes' => NULL, + + /* + * The pattern which should be used to create the users DN given the username. + * %username% in this pattern will be replaced with the users username. + * + * This option is not used if the search.enable option is set to TRUE. + */ + 'dnpattern' => 'uid=%username%,ou=people,dc=example,dc=org', + + /* + * As an alternative to specifying a pattern for the users DN, it is possible to + * search for the username in a set of attributes. This is enabled by this option. + */ + 'search.enable' => FALSE, + + /* + * The DN which will be used as a base for the search. + * This can be a single string, in which case only that DN is searched, or an + * array of strings, in which case they will be searched in the order given. + */ + 'search.base' => 'ou=people,dc=example,dc=org', + + /* + * The attribute(s) the username should match against. + * + * This is an array with one or more attribute names. Any of the attributes in + * the array may match the value the username. + */ + 'search.attributes' => array('uid', 'mail'), + + /* + * The username & password the simpleSAMLphp should bind to before searching. If + * this is left as NULL, no bind will be performed before searching. + */ + 'search.username' => NULL, + 'search.password' => NULL, + ), + + +You should update the name of this authentication source +(`example-ldap`) to have a name which makes sense to your organization. +You also need to update the `hostname` and `dnpattern` options. The +`hostname` should be the hostname of your LDAP server, and the +`dnpattern` should be a pattern which can be used to generate the `dn` +of a user with a given username. + +All other options have default values, and are not required. + +### Searching for a user ### + +Sometimes you cannot generate the users `dn` from the username, or you +may want to allow the user to authenticate with for example their email +address as the username. In this case, you can configure the LDAP +module to search for the users `dn` by searching for the username in +one or more attributes. + +To enable searching, you must set the `search.enable` option to `TRUE`. +You must then configure the `search.base` and the `search.attributes` +options. The `search.base`-option must be the `dn` which should be used +as the base/root of the search. The `search.attributes`-option is an +array with attributes the username should be matched against. + +The `dnpattern` option will not be used if searching is enabled. + +Some LDAP servers may require authentication before a search can be +performed. In this case, you should configure the `search.username` +and `search.password` options. The `search.username` option is a `dn` +which can be used to perform a search, and the `search.password` option +is the password for that `dn`. + + + +`ldap:LDAPMulti` +---------------- + +This module can be used if your organization has seperate groups with +seperate LDAP servers or seperate LDAP configurations. To use this +authentication module, open `config/authsources.php` in a text editor, +and add an entry which uses this module: + + 'example-ldapmulti' => array( + 'ldap:LDAPMulti', + + /* + * A list of available LDAP servers. The index is only an identifier, + * and can be any string. The value of each element is an array in the + * same format as an LDAP authentication source. + */ + 'employees' => array( + /* + * A short name/description for this group. Will be shown in a dropdown list + * when the user logs on. + * + * This option can be a string or an array with language => text mappings. + */ + 'description' => 'Employees', + + /* + * The rest of the options are the same as those available for + * the LDAP authentication source. + */ + 'hostname' => 'ldap.employees.example.org', + 'dnpattern' => 'uid=%username%,ou=employees,dc=example,dc=org', + ), + + 'students' => array( + 'description' => 'Students', + + 'hostname' => 'ldap.students.example.org', + 'dnpattern' => 'uid=%username%,ou=students,dc=example,dc=org', + ), + + ), + +The name of the authentication source (`example-ldapmulti`) should be +changed to something that makes sense for your organization. Each entry +in the configuration represents the configuration for one group of +users. The `description`-option in each group is the name of the group, +and will be shown to the user in a dropdown list on the login page. + +The `description`-option can also be an array with descriptions in +different languages: + + 'description' => array( + 'en' => 'Employees', + 'no' => 'Ansatte', + ), + +All options from the `ldap:LDAP` configuration can be used in each +group, and you should refer to the documentation for that module for +more information about available options. + + diff --git a/modules/ldap/lib/Auth/Source/LDAP.php b/modules/ldap/lib/Auth/Source/LDAP.php new file mode 100644 index 0000000000000000000000000000000000000000..f49ff333cd75cb343b74314556adf9f1b4b381a5 --- /dev/null +++ b/modules/ldap/lib/Auth/Source/LDAP.php @@ -0,0 +1,57 @@ +<?php + +/** + * LDAP authentication source. + * + * See the ldap-entry in config-templates/authsources.php for information about + * configuration of this authentication source. + * + * This class is based on www/auth/login.php. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_ldap_Auth_Source_LDAP extends sspmod_core_Auth_UserPassBase { + + /** + * A LDAP configuration object. + */ + private $ldapConfig; + + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) { + assert('is_array($info)'); + assert('is_array($config)'); + + /* Call the parent constructor first, as required by the interface. */ + parent::__construct($info, $config); + + $this->ldapConfig = new sspmod_ldap_ConfigHelper($config, + 'Authentication source ' . var_export($this->authId, TRUE)); + } + + + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @return array Associative array with the users attributes. + */ + protected function login($username, $password) { + assert('is_string($username)'); + assert('is_string($password)'); + + return $this->ldapConfig->login($username, $password); + } + +} + + +?> \ No newline at end of file diff --git a/modules/ldap/lib/Auth/Source/LDAPMulti.php b/modules/ldap/lib/Auth/Source/LDAPMulti.php new file mode 100644 index 0000000000000000000000000000000000000000..34f3fb425e9fa1e4898f644f43d1ed077f6c521a --- /dev/null +++ b/modules/ldap/lib/Auth/Source/LDAPMulti.php @@ -0,0 +1,93 @@ +<?php + +/** + * LDAP authentication source. + * + * See the ldap-entry in config-templates/authsources.php for information about + * configuration of this authentication source. + * + * This class is based on www/auth/login.php. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_ldap_Auth_Source_LDAPMulti extends sspmod_core_Auth_UserPassOrgBase { + + /** + * An array with descriptions for organizations. + */ + private $orgs; + + /** + * An array of organization IDs to LDAP configuration objects. + */ + private $ldapOrgs; + + + /** + * Constructor for this authentication source. + * + * @param array $info Information about this authentication source. + * @param array $config Configuration. + */ + public function __construct($info, $config) { + assert('is_array($info)'); + assert('is_array($config)'); + + /* Call the parent constructor first, as required by the interface. */ + parent::__construct($info, $config); + + $this->orgs = array(); + $this->ldapOrgs = array(); + foreach ($config as $orgId => $orgCfg) { + if (array_key_exists('description', $orgCfg)) { + $this->orgs[$orgId] = $orgCfg['description']; + } else { + $this->orgs[$orgId] = $orgId; + } + + $orgCfg = new sspmod_ldap_ConfigHelper($orgCfg, + 'Authentication source ' . var_export($this->authId, TRUE) . + ', organization ' . var_export($orgId, TRUE)); + $this->ldapOrgs[$orgId] = $orgCfg; + } + } + + + /** + * Attempt to log in using the given username and password. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @param string $org The organization the user chose. + * @return array Associative array with the users attributes. + */ + protected function login($username, $password, $org) { + assert('is_string($username)'); + assert('is_string($password)'); + + if (!array_key_exists($org, $this->ldapOrgs)) { + /* The user has selected an organization which doesn't exist anymore. */ + SimpleSAML_Logger::warning('Authentication source ' . var_export($this->authId, TRUE) . + ': Organization seems to have disappeared while the user logged in.' . + ' Organization was ' . var_export($org, TRUE)); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + + return $this->ldapOrgs[$org]->login($username, $password); + } + + + /** + * Retrieve list of organizations. + * + * @return array Associative array with the organizations. + */ + protected function getOrganizations() { + return $this->orgs; + } + +} + + +?> \ No newline at end of file diff --git a/modules/ldap/lib/ConfigHelper.php b/modules/ldap/lib/ConfigHelper.php new file mode 100644 index 0000000000000000000000000000000000000000..59e43f4557381aa217337b709c3b437927c8a1ec --- /dev/null +++ b/modules/ldap/lib/ConfigHelper.php @@ -0,0 +1,158 @@ +<?php + +/** + * LDAP authentication source configuration parser. + * + * See the ldap-entry in config-templates/authsources.php for information about + * configuration of these options. + * + * @package simpleSAMLphp + * @version $Id$ + */ +class sspmod_ldap_ConfigHelper { + + + /** + * String with the location of this configuration. + * Used for error reporting. + */ + private $location; + + + /** + * The hostname of the LDAP server. + */ + private $hostname; + + + /** + * Whether we should use TLS/SSL when contacting the LDAP server. + */ + private $enableTLS; + + + /** + * Whether we need to search for the users DN. + */ + private $searchEnable; + + + /** + * The username we should bind with before we can search for the user. + */ + private $searchUsername; + + + /** + * The password we should bind with before we can search for the user. + */ + private $searchPassword; + + + /** + * Array with the base DN(s) for the search. + */ + private $searchBase; + + + /** + * The attributes which should match the username. + */ + private $searchAttributes; + + + /** + * The DN pattern we should use to create the DN from the username. + */ + private $dnPattern; + + + /** + * The attributes we should fetch. Can be NULL in which case we will fetch all attributes. + */ + private $attributes; + + + + /** + * Constructor for this configuration parser. + * + * @param array $config Configuration. + * @param string $location The location of this configuration. Used for error reporting. + */ + public function __construct($config, $location) { + assert('is_array($config)'); + assert('is_string($location)'); + + $this->location = $location; + + /* Parse configuration. */ + $config = SimpleSAML_Configuration::loadFromArray($config, $location); + + $this->hostname = $config->getString('hostname'); + $this->enableTLS = $config->getBoolean('enable_tls', FALSE); + $this->searchEnable = $config->getBoolean('search.enable', FALSE); + + if ($this->searchEnable) { + $this->searchUsername = $config->getString('search.username', NULL); + if ($this->searchUsername !== NULL) { + $this->searchPassword = $config->getString('search.password'); + } + + $this->searchBase = $config->getString('search.base'); + $this->searchAttributes = $config->getArray('search.attributes'); + + } else { + $this->dnPattern = $config->getString('dnpattern'); + } + + $this->attributes = $config->getArray('attributes', NULL); + } + + + /** + * Attempt to log in using the given username and password. + * + * Will throw a SimpleSAML_Error_Error('WRONGUSERPASS') if the username or password is wrong. + * If there is a configuration problem, an Exception will be thrown. + * + * @param string $username The username the user wrote. + * @param string $password The password the user wrote. + * @return array Associative array with the users attributes. + */ + public function login($username, $password) { + assert('is_string($username)'); + assert('is_string($password)'); + + $ldap = new SimpleSAML_Auth_LDAP($this->hostname, $this->enableTLS); + + if (!$this->searchEnable) { + $ldapusername = addcslashes($username, ',+"\\<>;*'); + $dn = str_replace('%username%', $ldapusername, $this->dnPattern); + } else { + if ($this->searchUsername !== NULL) { + if(!$ldap->bind($this->searchUsername, $this->searchPassword)) { + throw new Exception('Error authenticating using search username & password.'); + } + } + + $dn = $ldap->searchfordn($this->searchBase, $this->searchAttributes, $username, TRUE); + if ($dn === NULL) { + /* User not found with search. */ + SimpleSAML_Logger::info($this->location . ': Unable to find users DN. username=\'' . $username . '\''); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + } + + if (!$ldap->bind($dn, $password)) { + SimpleSAML_Logger::info($this->location . ': '. $username . ' failed to authenticate. DN=' . $dn); + throw new SimpleSAML_Error_Error('WRONGUSERPASS'); + } + + return $ldap->getAttributes($dn, $this->attributes); + } + +} + + +?> \ No newline at end of file