diff options
Diffstat (limited to 'app/Core/Ldap')
| -rw-r--r-- | app/Core/Ldap/Client.php | 165 | ||||
| -rw-r--r-- | app/Core/Ldap/ClientException.php | 15 | ||||
| -rw-r--r-- | app/Core/Ldap/Entries.php | 63 | ||||
| -rw-r--r-- | app/Core/Ldap/Entry.php | 91 | ||||
| -rw-r--r-- | app/Core/Ldap/Group.php | 131 | ||||
| -rw-r--r-- | app/Core/Ldap/Query.php | 87 | ||||
| -rw-r--r-- | app/Core/Ldap/User.php | 224 |
7 files changed, 776 insertions, 0 deletions
diff --git a/app/Core/Ldap/Client.php b/app/Core/Ldap/Client.php new file mode 100644 index 00000000..63149ae3 --- /dev/null +++ b/app/Core/Ldap/Client.php @@ -0,0 +1,165 @@ +<?php + +namespace Kanboard\Core\Ldap; + +use LogicException; + +/** + * LDAP Client + * + * @package ldap + * @author Frederic Guillot + */ +class Client +{ + /** + * LDAP resource + * + * @access protected + * @var resource + */ + protected $ldap; + + /** + * Establish LDAP connection + * + * @static + * @access public + * @param string $username + * @param string $password + * @return Client + */ + public static function connect($username = null, $password = null) + { + $client = new self; + $client->open($client->getLdapServer()); + $username = $username ?: $client->getLdapUsername(); + $password = $password ?: $client->getLdapPassword(); + + if (empty($username) && empty($password)) { + $client->useAnonymousAuthentication(); + } else { + $client->authenticate($username, $password); + } + + return $client; + } + + /** + * Get server connection + * + * @access public + * @return resource + */ + public function getConnection() + { + return $this->ldap; + } + + /** + * Establish server connection + * + * @access public + * @param string $server LDAP server hostname or IP + * @param integer $port LDAP port + * @param boolean $tls Start TLS + * @param boolean $verify Skip SSL certificate verification + * @return Client + */ + public function open($server, $port = LDAP_PORT, $tls = LDAP_START_TLS, $verify = LDAP_SSL_VERIFY) + { + if (! function_exists('ldap_connect')) { + throw new ClientException('LDAP: The PHP LDAP extension is required'); + } + + if (! $verify) { + putenv('LDAPTLS_REQCERT=never'); + } + + $this->ldap = ldap_connect($server, $port); + + if ($this->ldap === false) { + throw new ClientException('LDAP: Unable to connect to the LDAP server'); + } + + ldap_set_option($this->ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + ldap_set_option($this->ldap, LDAP_OPT_REFERRALS, 0); + ldap_set_option($this->ldap, LDAP_OPT_NETWORK_TIMEOUT, 1); + ldap_set_option($this->ldap, LDAP_OPT_TIMELIMIT, 1); + + if ($tls && ! @ldap_start_tls($this->ldap)) { + throw new ClientException('LDAP: Unable to start TLS'); + } + + return $this; + } + + /** + * Anonymous authentication + * + * @access public + * @return boolean + */ + public function useAnonymousAuthentication() + { + if (! @ldap_bind($this->ldap)) { + throw new ClientException('Unable to perform anonymous binding'); + } + + return true; + } + + /** + * Authentication with username/password + * + * @access public + * @param string $bind_rdn + * @param string $bind_password + * @return boolean + */ + public function authenticate($bind_rdn, $bind_password) + { + if (! @ldap_bind($this->ldap, $bind_rdn, $bind_password)) { + throw new ClientException('LDAP authentication failure for "'.$bind_rdn.'"'); + } + + return true; + } + + /** + * Get LDAP server name + * + * @access public + * @return string + */ + public function getLdapServer() + { + if (! LDAP_SERVER) { + throw new LogicException('LDAP server not configured, check the parameter LDAP_SERVER'); + } + + return LDAP_SERVER; + } + + /** + * Get LDAP username (proxy auth) + * + * @access public + * @return string + */ + public function getLdapUsername() + { + return LDAP_USERNAME; + } + + /** + * Get LDAP password (proxy auth) + * + * @access public + * @return string + */ + public function getLdapPassword() + { + return LDAP_PASSWORD; + } +} diff --git a/app/Core/Ldap/ClientException.php b/app/Core/Ldap/ClientException.php new file mode 100644 index 00000000..a0f9f842 --- /dev/null +++ b/app/Core/Ldap/ClientException.php @@ -0,0 +1,15 @@ +<?php + +namespace Kanboard\Core\Ldap; + +use Exception; + +/** + * LDAP Client Exception + * + * @package ldap + * @author Frederic Guillot + */ +class ClientException extends Exception +{ +} diff --git a/app/Core/Ldap/Entries.php b/app/Core/Ldap/Entries.php new file mode 100644 index 00000000..0e779342 --- /dev/null +++ b/app/Core/Ldap/Entries.php @@ -0,0 +1,63 @@ +<?php + +namespace Kanboard\Core\Ldap; + +/** + * LDAP Entries + * + * @package ldap + * @author Frederic Guillot + */ +class Entries +{ + /** + * LDAP entries + * + * @access protected + * @var array + */ + protected $entries = array(); + + /** + * Constructor + * + * @access public + * @param array $entries + */ + public function __construct(array $entries) + { + $this->entries = $entries; + } + + /** + * Get all entries + * + * @access public + * @return Entry[] + */ + public function getAll() + { + $entities = array(); + + if (! isset($this->entries['count'])) { + return $entities; + } + + for ($i = 0; $i < $this->entries['count']; $i++) { + $entities[] = new Entry($this->entries[$i]); + } + + return $entities; + } + + /** + * Get first entry + * + * @access public + * @return Entry + */ + public function getFirstEntry() + { + return new Entry(isset($this->entries[0]) ? $this->entries[0] : array()); + } +} diff --git a/app/Core/Ldap/Entry.php b/app/Core/Ldap/Entry.php new file mode 100644 index 00000000..0b99a58b --- /dev/null +++ b/app/Core/Ldap/Entry.php @@ -0,0 +1,91 @@ +<?php + +namespace Kanboard\Core\Ldap; + +/** + * LDAP Entry + * + * @package ldap + * @author Frederic Guillot + */ +class Entry +{ + /** + * LDAP entry + * + * @access protected + * @var array + */ + protected $entry = array(); + + /** + * Constructor + * + * @access public + * @param array $entry + */ + public function __construct(array $entry) + { + $this->entry = $entry; + } + + /** + * Get all attribute values + * + * @access public + * @param string $attribute + * @return string[] + */ + public function getAll($attribute) + { + $attributes = array(); + + if (! isset($this->entry[$attribute]['count'])) { + return $attributes; + } + + for ($i = 0; $i < $this->entry[$attribute]['count']; $i++) { + $attributes[] = $this->entry[$attribute][$i]; + } + + return $attributes; + } + + /** + * Get first attribute value + * + * @access public + * @param string $attribute + * @param string $default + * @return string + */ + public function getFirstValue($attribute, $default = '') + { + return isset($this->entry[$attribute][0]) ? $this->entry[$attribute][0] : $default; + } + + /** + * Get entry distinguished name + * + * @access public + * @return string + */ + public function getDn() + { + return isset($this->entry['dn']) ? $this->entry['dn'] : ''; + } + + /** + * Return true if the given value exists in attribute list + * + * @access public + * @param string $attribute + * @param string $value + * @return boolean + */ + public function hasValue($attribute, $value) + { + $attributes = $this->getAll($attribute); + return in_array($value, $attributes); + } +} diff --git a/app/Core/Ldap/Group.php b/app/Core/Ldap/Group.php new file mode 100644 index 00000000..634d47ee --- /dev/null +++ b/app/Core/Ldap/Group.php @@ -0,0 +1,131 @@ +<?php + +namespace Kanboard\Core\Ldap; + +use LogicException; +use Kanboard\Group\LdapGroupProvider; + +/** + * LDAP Group Finder + * + * @package ldap + * @author Frederic Guillot + */ +class Group +{ + /** + * Query + * + * @access protected + * @var Query + */ + protected $query; + + /** + * Constructor + * + * @access public + * @param Query $query + */ + public function __construct(Query $query) + { + $this->query = $query; + } + + /** + * Get groups + * + * @static + * @access public + * @param Client $client + * @param string $query + * @return array + */ + public static function getGroups(Client $client, $query) + { + $className = get_called_class(); + $self = new $className(new Query($client)); + return $self->find($query); + } + + /** + * Find groups + * + * @access public + * @param string $query + * @return array + */ + public function find($query) + { + $this->query->execute($this->getBasDn(), $query, $this->getAttributes()); + $groups = array(); + + if ($this->query->hasResult()) { + $groups = $this->build(); + } + + return $groups; + } + + /** + * Build groups list + * + * @access protected + * @return array + */ + protected function build() + { + $groups = array(); + + foreach ($this->query->getEntries()->getAll() as $entry) { + $groups[] = new LdapGroupProvider($entry->getDn(), $entry->getFirstValue($this->getAttributeName())); + } + + return $groups; + } + + /** + * Ge the list of attributes to fetch when reading the LDAP group entry + * + * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong" + * + * @access public + * @return array + */ + public function getAttributes() + { + return array_values(array_filter(array( + $this->getAttributeName(), + ))); + } + + /** + * Get LDAP group name attribute + * + * @access public + * @return string + */ + public function getAttributeName() + { + if (! LDAP_GROUP_ATTRIBUTE_NAME) { + throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_GROUP_ATTRIBUTE_NAME'); + } + + return LDAP_GROUP_ATTRIBUTE_NAME; + } + + /** + * Get LDAP group base DN + * + * @access public + * @return string + */ + public function getBasDn() + { + if (! LDAP_GROUP_BASE_DN) { + throw new LogicException('LDAP group base DN empty, check the parameter LDAP_GROUP_BASE_DN'); + } + + return LDAP_GROUP_BASE_DN; + } +} diff --git a/app/Core/Ldap/Query.php b/app/Core/Ldap/Query.php new file mode 100644 index 00000000..e03495ec --- /dev/null +++ b/app/Core/Ldap/Query.php @@ -0,0 +1,87 @@ +<?php + +namespace Kanboard\Core\Ldap; + +/** + * LDAP Query + * + * @package ldap + * @author Frederic Guillot + */ +class Query +{ + /** + * LDAP client + * + * @access protected + * @var Client + */ + protected $client = null; + + /** + * Query result + * + * @access protected + * @var array + */ + protected $entries = array(); + + /** + * Constructor + * + * @access public + * @param Client $client + */ + public function __construct(Client $client) + { + $this->client = $client; + } + + /** + * Execute query + * + * @access public + * @param string $baseDn + * @param string $filter + * @param array $attributes + * @return Query + */ + public function execute($baseDn, $filter, array $attributes) + { + $sr = ldap_search($this->client->getConnection(), $baseDn, $filter, $attributes); + if ($sr === false) { + return $this; + } + + $entries = ldap_get_entries($this->client->getConnection(), $sr); + if ($entries === false || count($entries) === 0 || $entries['count'] == 0) { + return $this; + } + + $this->entries = $entries; + + return $this; + } + + /** + * Return true if the query returned a result + * + * @access public + * @return boolean + */ + public function hasResult() + { + return ! empty($this->entries); + } + + /** + * Get LDAP Entries + * + * @access public + * @return Entities + */ + public function getEntries() + { + return new Entries($this->entries); + } +} diff --git a/app/Core/Ldap/User.php b/app/Core/Ldap/User.php new file mode 100644 index 00000000..d36d6f34 --- /dev/null +++ b/app/Core/Ldap/User.php @@ -0,0 +1,224 @@ +<?php + +namespace Kanboard\Core\Ldap; + +use LogicException; +use Kanboard\Core\Security\Role; +use Kanboard\User\LdapUserProvider; + +/** + * LDAP User Finder + * + * @package ldap + * @author Frederic Guillot + */ +class User +{ + /** + * Query + * + * @access protected + * @var Query + */ + protected $query; + + /** + * Constructor + * + * @access public + * @param Query $query + */ + public function __construct(Query $query) + { + $this->query = $query; + } + + /** + * Get user profile + * + * @static + * @access public + * @param Client $client + * @param string $username + * @return LdapUserProvider + */ + public static function getUser(Client $client, $username) + { + $className = get_called_class(); + $self = new $className(new Query($client)); + return $self->find($self->getLdapUserPattern($username)); + } + + /** + * Find user + * + * @access public + * @param string $query + * @return null|LdapUserProvider + */ + public function find($query) + { + $this->query->execute($this->getBasDn(), $query, $this->getAttributes()); + $user = null; + + if ($this->query->hasResult()) { + $user = $this->build(); + } + + return $user; + } + + /** + * Build user profile + * + * @access protected + * @return LdapUserProvider + */ + protected function build() + { + $entry = $this->query->getEntries()->getFirstEntry(); + $role = Role::APP_USER; + + if ($entry->hasValue($this->getAttributeGroup(), $this->getGroupAdminDn())) { + $role = Role::APP_ADMIN; + } elseif ($entry->hasValue($this->getAttributeGroup(), $this->getGroupManagerDn())) { + $role = Role::APP_MANAGER; + } + + return new LdapUserProvider( + $entry->getDn(), + $entry->getFirstValue($this->getAttributeUsername()), + $entry->getFirstValue($this->getAttributeName()), + $entry->getFirstValue($this->getAttributeEmail()), + $role, + $entry->getAll($this->getAttributeGroup()) + ); + } + + /** + * Ge the list of attributes to fetch when reading the LDAP user entry + * + * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong" + * + * @access public + * @return array + */ + public function getAttributes() + { + return array_values(array_filter(array( + $this->getAttributeUsername(), + $this->getAttributeName(), + $this->getAttributeEmail(), + $this->getAttributeGroup(), + ))); + } + + /** + * Get LDAP account id attribute + * + * @access public + * @return string + */ + public function getAttributeUsername() + { + if (! LDAP_USER_ATTRIBUTE_USERNAME) { + throw new LogicException('LDAP username attribute empty, check the parameter LDAP_USER_ATTRIBUTE_USERNAME'); + } + + return LDAP_USER_ATTRIBUTE_USERNAME; + } + + /** + * Get LDAP user name attribute + * + * @access public + * @return string + */ + public function getAttributeName() + { + if (! LDAP_USER_ATTRIBUTE_FULLNAME) { + throw new LogicException('LDAP full name attribute empty, check the parameter LDAP_USER_ATTRIBUTE_FULLNAME'); + } + + return LDAP_USER_ATTRIBUTE_FULLNAME; + } + + /** + * Get LDAP account email attribute + * + * @access public + * @return string + */ + public function getAttributeEmail() + { + if (! LDAP_USER_ATTRIBUTE_EMAIL) { + throw new LogicException('LDAP email attribute empty, check the parameter LDAP_USER_ATTRIBUTE_EMAIL'); + } + + return LDAP_USER_ATTRIBUTE_EMAIL; + } + + /** + * Get LDAP account memberof attribute + * + * @access public + * @return string + */ + public function getAttributeGroup() + { + return LDAP_USER_ATTRIBUTE_GROUPS; + } + + /** + * Get LDAP admin group DN + * + * @access public + * @return string + */ + public function getGroupAdminDn() + { + return LDAP_GROUP_ADMIN_DN; + } + + /** + * Get LDAP application manager group DN + * + * @access public + * @return string + */ + public function getGroupManagerDn() + { + return LDAP_GROUP_MANAGER_DN; + } + + /** + * Get LDAP user base DN + * + * @access public + * @return string + */ + public function getBasDn() + { + if (! LDAP_USER_BASE_DN) { + throw new LogicException('LDAP user base DN empty, check the parameter LDAP_USER_BASE_DN'); + } + + return LDAP_USER_BASE_DN; + } + + /** + * Get LDAP user pattern + * + * @access public + * @param string $username + * @return string + */ + public function getLdapUserPattern($username) + { + if (! LDAP_USER_FILTER) { + throw new LogicException('LDAP user filter empty, check the parameter LDAP_USER_FILTER'); + } + + return sprintf(LDAP_USER_FILTER, $username); + } +} |
