From c2b47815a9e34a504dfac1f6991747924db8cc28 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Wed, 4 Jan 2012 19:20:54 +0000 Subject: [PATCH] Add LDAP Auth backend --- .../Auth/Plugin/LDAP.class.php | 116 +++++++++++++++ .../Auth/Plugin/LDAP/User.class.php | 139 ++++++++++++++++++ .../lib/SihnonFramework/Exceptions.class.php | 5 + 3 files changed, 260 insertions(+) create mode 100644 source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php create mode 100644 source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php diff --git a/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php b/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php new file mode 100644 index 0000000..edab9da --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/LDAP.class.php @@ -0,0 +1,116 @@ +config = $config; + + $this->initInstance(); + } + + protected function initInstance() { + $this->ldap = ldap_connect($this->config->get('auth.LDAP.servers'), $this->config->get('auth.LDAP.port', 389)); + if ( ! $this->ldap) { + throw new SihnonFramework_Exception_LDAPConnectionFailed(); + } + + if ($this->config->get('auth.LDAP.start-tls', false)) { + if ( ! ldap_start_tls($this->ldap)) { + throw new Sihnon_Exception_LDAPSecureConnectionFailed(); + } + } + + $search_dn = $this->config->get('auth.LDAP.search-dn', null); + $search_password = $this->config->get('auth.LDAP.search-password', null); + if ( ! ldap_bind($this->ldap, $search_dn, $search_password)) { + var_dump("Failed to bind as", $search_dn, $search_password); + throw new SihnonFramework_Exception_LDAPBindFailed(); + } + + Sihnon_Auth_Plugin_LDAP_User::init( + $this->ldap, + $this->config->get('auth.LDAP.user-base-dn'), + $this->config->get('auth.LDAP.group-base-dn'), + $this->config->get('auth.LDAP.recursive-search', false) + ); + } + + /* + * IPlugin methods + */ + + public static function create(Sihnon_Config $config) { + return new self($config); + } + + public function userExists($username) { + return Sihnon_Auth_Plugin_LDAP_User::exists($username); + } + + public function listUsers() { + return Sihnon_Auth_Plugin_LDAP_User::all(); + } + + public function authenticate($username, $password) { + $user = Sihnon_Auth_Plugin_LDAP_User::load($username); + + if ( ! $user->checkPassword($password)) { + throw new Sihnon_Exception_IncorrectPassword(); + } + + return $user; + } + + public function authenticateSession($username) { + return Sihnon_Auth_Plugin_LDAP_User::load($username); + } + + /* + * IPermissionable methods + */ + + public function isAdministrator(Sihnon_Auth_IUser $user) { + return $user->isAdministrator(); + } + + public function hasPermission(Sihnon_Auth_IUser $user, $permission) { + return $user->hasPermission($permission); + } + public static function ldapEscape($input_str, $for_dn = false) { + // Taken from Douglas Davis at http://php.sihnon.net/manual/en/function.ldap-search.php#90158 + // see: + // RFC2254 + // http://msdn.microsoft.com/en-us/library/ms675768(VS.85).aspx + // http://www-03.ibm.com/systems/i/software/ldap/underdn.html + + $str = $input_str; + if ( ! is_array($str)) { + $str = array($str); + } + + if ($for_dn) { + $metaChars = array(',', '=', '+', '<', '>', ';', '\\', '"', '#'); + } + else { + $metaChars = array('*', '(', ')', '\\', chr(0)); + } + + $quotedMetaChars = array(); + foreach ($metaChars as $key => $value) { + $quotedMetaChars[$key] = '\\'.str_pad(dechex(ord($value)), 2, '0'); + } + $str = str_replace($metaChars,$quotedMetaChars,$str); //replace them + + return is_array($input_str) ? $str : $str[0]; + } + +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php b/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php new file mode 100644 index 0000000..370272f --- /dev/null +++ b/source/lib/SihnonFramework/Auth/Plugin/LDAP/User.class.php @@ -0,0 +1,139 @@ +fullname = $result['cn'][0]; + $user->username = $result['uid'][0]; + + return $user; + } + + public static function load($username) { + $ldap_username = Sihnon_Auth_Plugin_LDAP::ldapEscape($username); + $filter = "(&(objectClass=posixAccount)(uid={$ldap_username}))"; + + $search = ldap_search(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0, 1); + $result = ldap_get_entries(static::$ldap, $search); + + if ($result['count'] != 1) { + throw new Sihnon_Exception_UnknownUser($username); + } + + return static::fromLDAP($result[0]); + } + + public static function all() { + $filter = "(objectClass=posixAccount)"; + + $search = null; + if (static::$recursive_search) { + $search = ldap_search(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0); + } else { + $search = ldap_list(static::$ldap, static::$user_base_dn, $filter, array('cn', 'uid'), 0); + } + $result = ldap_get_entries(static::$ldap, $search); + + $users = array(); + for ($i = 0, $l = $result['count']; $i < $l; ++$i) { + $users[] = static::fromLDAP($result[$i]); + } + + return $users; + } + + public function username() { + return $this->username; + } + + public function checkPassword($password) { + $ldap_user_dn = Sihnon_Auth_Plugin_LDAP::ldapEscape($this->fullname, true); + return ldap_bind(static::$ldap, "cn={$ldap_user_dn},".static::$base_dn, $password); + } + + public function changePassword($new_password) { + throw new Sihnon_Exception_NotImplemented(); + } + + public function isAdministrator() { + return $this->hasPermission('wheel'); + } + + public function hasPermission($permission) { + return in_array($permission, $this->permissions()); + } + + public function permissions() { + if ($this->groups === null) { + $ldap_username = Sihnon_Auth_Plugin_LDAP::ldapEscape($this->username); + + $filter = "(&(objectClass=posixGroup)(memberUid={$ldap_username}))"; + + $search = null; + if (static::$recursive_search) { + $search = ldap_search(static::$ldap, static::$group_base_dn, $filter, array('cn'), 0); + } else { + $search = ldap_list(static::$ldap, static::$group_base_dn, $filter, array('cn'), 0); + } + $result = ldap_get_entries(static::$ldap, $search); + + $this->groups = array(); + for ($i = 0, $l = $result['count']; $i < $l; ++$i) { + $this->groups[] = $result[$i]['cn'][0]; + } + + } + + return $this->groups; + } + + public function __get($name) { + switch ($name) { + case 'username': { + return $this->username; + } break; + + case 'fullname': { + return $this->fullname; + } break; + + default: { + throw new Sihnon_Exception_InvalidProperty($name); + } break; + } + } + + public function __set($name, $value) { + throw new Sihnon_Exception_NotImplemented(); + } +} + +?> \ No newline at end of file diff --git a/source/lib/SihnonFramework/Exceptions.class.php b/source/lib/SihnonFramework/Exceptions.class.php index c67f4d3..4dfe670 100644 --- a/source/lib/SihnonFramework/Exceptions.class.php +++ b/source/lib/SihnonFramework/Exceptions.class.php @@ -52,4 +52,9 @@ class SihnonFramework_Exception_DaemonException extends SihnonFramework_E class SihnonFramework_Exception_AlreadyRunning extends SihnonFramework_Exception_DaemonException {}; class SihnonFramework_Exception_LockingFailed extends SihnonFramework_Exception_DaemonException {}; +class SihnonFramework_Exception_LDAPException extends SihnonFramework_Exception {}; +class SihnonFramework_Exception_LDAPConnectionFailed extends SihnonFramework_Exception_LDAPException {}; +class SihnonFramework_Exception_LDAPSecureConnectionFailed extends SihnonFramework_Exception_LDAPException {}; +class SihnonFramework_Exception_LDAPBindFailed extends SihnonFramework_Exception_LDAPException {}; + ?>