View source
<?php
namespace Drupal\ldap_servers\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\ldap_servers\Helper\ConversionHelper;
use Drupal\ldap_servers\Helper\CredentialsStorage;
use Drupal\ldap_servers\LdapProtocolInterface;
use Drupal\ldap_servers\Helper\MassageAttributes;
use Drupal\ldap_servers\ServerInterface;
use Drupal\ldap_servers\Processor\TokenProcessor;
use Drupal\user\Entity\User;
class Server extends ConfigEntityBase implements ServerInterface, LdapProtocolInterface {
const LDAP_OPT_DIAGNOSTIC_MESSAGE_BYTE = 0x32;
const LDAP_SERVER_LDAP_QUERY_CHUNK = 50;
const LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT = 10;
const SCOPE_BASE = 1;
const SCOPE_ONE_LEVEL = 2;
const SCOPE_SUBTREE = 3;
protected $id;
protected $label;
protected $connection = FALSE;
protected $logger;
protected $detailLog;
protected $tokenProcessor;
protected $moduleHandler;
protected $searchPageStart = 0;
protected $searchPageEnd = NULL;
public function __construct(array $values, $entity_type) {
parent::__construct($values, $entity_type);
$this->logger = \Drupal::logger('ldap_servers');
$this->detailLog = \Drupal::service('ldap.detail_log');
$this->tokenProcessor = \Drupal::service('ldap.token_processor');
$this->moduleHandler = \Drupal::service('module_handler');
}
public function getFormattedBind() {
switch ($this
->get('bind_method')) {
case 'service_account':
default:
$namedBind = t('service account bind');
break;
case 'user':
$namedBind = t('user credentials bind');
break;
case 'anon':
$namedBind = t('anonymous bind (search), then user credentials');
break;
case 'anon_user':
$namedBind = t('anonymous bind');
break;
}
return $namedBind;
}
public function connect() {
if (!function_exists('ldap_connect')) {
$this->logger
->error('PHP LDAP extension not found, aborting.');
return self::LDAP_NOT_SUPPORTED;
}
$this->connection = ldap_connect(self::get('address'), self::get('port'));
if (!$this->connection) {
$this->logger
->notice('LDAP Connect failure to @address on port @port.', [
'@address' => self::get('address'),
'@port' => self::get('port'),
]);
return self::LDAP_CONNECT_ERROR;
}
ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0);
ldap_set_option($this->connection, LDAP_OPT_NETWORK_TIMEOUT, self::get('timeout'));
if (self::get('tls')) {
ldap_get_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, $protocolVersion);
if ($protocolVersion == -1) {
$this->logger
->notice('Could not get LDAP protocol version.');
return self::LDAP_PROTOCOL_ERROR;
}
if ($protocolVersion != 3) {
$this->logger
->notice('Could not start TLS, only supported by LDAP v3.');
return self::LDAP_CONNECT_ERROR;
}
elseif (!function_exists('ldap_start_tls')) {
$this->logger
->notice('Could not start TLS. It does not seem to be supported by this PHP setup.');
return self::LDAP_CONNECT_ERROR;
}
elseif (!ldap_start_tls($this->connection)) {
$this->logger
->notice('Could not start TLS. (Error @errno: @error).', [
'@errno' => ldap_errno($this->connection),
'@error' => ldap_error($this->connection),
]);
return self::LDAP_CONNECT_ERROR;
}
}
return self::LDAP_SUCCESS;
}
public function bind() {
if (!$this->connection) {
$this->logger
->error("LDAP bind failure. Not connected to LDAP server.");
return self::LDAP_CONNECT_ERROR;
}
$validMethods = [
'service_account',
'user',
'anon',
'anon_user',
];
if (!in_array($this
->get('bind_method'), $validMethods)) {
$this->logger
->error("Bind method missing.");
return self::LDAP_CONNECT_ERROR;
}
if ($this
->get('bind_method') == 'anon') {
$anon_bind = TRUE;
}
elseif ($this
->get('bind_method') == 'anon_user') {
if (CredentialsStorage::validateCredentials()) {
$anon_bind = FALSE;
}
else {
$anon_bind = TRUE;
}
}
else {
$anon_bind = FALSE;
}
if ($anon_bind) {
$response = $this
->anonymousBind();
}
else {
$response = $this
->nonAnonymousBind();
}
return $response;
}
private function anonymousBind() {
if (@(!ldap_bind($this->connection))) {
$this->detailLog
->log("LDAP anonymous bind error. Error %error", [
'%error' => $this
->formattedError($this
->ldapErrorNumber()),
]);
$response = ldap_errno($this->connection);
}
else {
$response = self::LDAP_SUCCESS;
}
return $response;
}
private function nonAnonymousBind() {
$userDn = $this
->get('binddn');
$password = $this
->get('bindpw');
if (CredentialsStorage::validateCredentials()) {
$userDn = CredentialsStorage::getUserDn();
$password = CredentialsStorage::getPassword();
}
if (mb_strlen($password) == 0 || mb_strlen($userDn) == 0) {
$this->logger
->notice("LDAP bind failure due to missing credentials for user userdn=%userdn, pass=%pass.", [
'%userdn' => $userDn,
'%pass' => $password,
]);
$response = self::LDAP_LOCAL_ERROR;
}
if (@(!ldap_bind($this->connection, $userDn, $password))) {
$this->detailLog
->log("LDAP bind failure for user %user. Error %errno: %error", [
'%user' => $userDn,
'%errno' => ldap_errno($this->connection),
'%error' => ldap_error($this->connection),
]);
$response = ldap_errno($this->connection);
}
else {
$response = self::LDAP_SUCCESS;
}
return $response;
}
public function disconnect() {
if (!$this->connection) {
}
else {
ldap_unbind($this->connection);
$this->connection = NULL;
}
}
public function connectAndBindIfNotAlready() {
if (!$this->connection) {
$this
->connect();
$this
->bind();
}
}
public function checkDnExistsIncludeData($dn, array $attributes) {
$params = [
'base_dn' => $dn,
'attributes' => $attributes,
'attrsonly' => FALSE,
'filter' => '(objectclass=*)',
'sizelimit' => 0,
'timelimit' => 0,
'deref' => NULL,
];
$result = $this
->ldapQuery(Server::SCOPE_BASE, $params);
if ($result !== FALSE) {
$entries = @ldap_get_entries($this->connection, $result);
if ($entries !== FALSE && $entries['count'] > 0) {
return $entries[0];
}
}
return FALSE;
}
public function checkDnExists($dn) {
$params = [
'base_dn' => $dn,
'attributes' => [
'objectclass',
],
'attrsonly' => FALSE,
'filter' => '(objectclass=*)',
'sizelimit' => 0,
'timelimit' => 0,
'deref' => NULL,
];
$result = $this
->ldapQuery(Server::SCOPE_BASE, $params);
if ($result !== FALSE) {
$entries = @ldap_get_entries($this->connection, $result);
if ($entries !== FALSE && $entries['count'] > 0) {
return TRUE;
}
}
return FALSE;
}
public function countEntries($ldap_result) {
return ldap_count_entries($this->connection, $ldap_result);
}
public function createLdapEntry(array $attributes, $dn = NULL) {
$this
->connectAndBindIfNotAlready();
if (isset($attributes['dn'])) {
$dn = $attributes['dn'];
unset($attributes['dn']);
}
elseif (!$dn) {
return FALSE;
}
if (!empty($attributes['unicodePwd']) && $this
->get('type') == 'ad') {
$attributes['unicodePwd'] = $this
->convertPasswordForActiveDirectoryunicodePwd($attributes['unicodePwd']);
}
$result = @ldap_add($this->connection, $dn, $attributes);
if (!$result) {
ldap_get_option($this->connection, self::LDAP_OPT_DIAGNOSTIC_MESSAGE_BYTE, $ldap_additional_info);
$this->logger
->error("LDAP Server ldap_add(%dn) Error Server ID = %id, LDAP Error %ldap_error. LDAP Additional info: %ldap_additional_info", [
'%dn' => $dn,
'%id' => $this
->id(),
'%ldap_error' => $this
->formattedError($this
->ldapErrorNumber()),
'%ldap_additional_info' => $ldap_additional_info,
]);
}
return $result;
}
public function deleteLdapEntry($dn) {
$this
->connectAndBindIfNotAlready();
$result = @ldap_delete($this->connection, $dn);
if (!$result) {
$this->logger
->error("LDAP Server delete(%dn) in LdapServer::delete() Error Server ID = %id, LDAP Error %ldap_error.", [
'%dn' => $dn,
'%id' => $this
->id(),
'%ldap_error' => $this
->formattedError($this
->ldapErrorNumber()),
]);
}
return $result;
}
public static function ldapEscape($string) {
if (function_exists('ldap_escape')) {
return ldap_escape($string);
}
else {
return str_replace([
'*',
'\\',
'(',
')',
], [
'\\*',
'\\\\',
'\\(',
'\\)',
], $string);
}
}
public static function removeUnchangedAttributes(array $newEntry, array $oldEntry) {
foreach ($newEntry as $key => $newValue) {
$oldValue = FALSE;
$oldValueIsScalar = NULL;
$keyLowercased = mb_strtolower($key);
if (isset($oldEntry[$keyLowercased])) {
if ($oldEntry[$keyLowercased]['count'] == 1) {
$oldValue = $oldEntry[$keyLowercased][0];
$oldValueIsScalar = TRUE;
}
else {
unset($oldEntry[$keyLowercased]['count']);
$oldValue = $oldEntry[$keyLowercased];
$oldValueIsScalar = FALSE;
}
}
if (is_array($newValue) && is_array($oldValue) && count(array_diff($newValue, $oldValue)) == 0) {
unset($newEntry[$key]);
}
elseif ($oldValueIsScalar && !is_array($newValue) && mb_strtolower($oldValue) == mb_strtolower($newValue)) {
unset($newEntry[$key]);
}
}
return $newEntry;
}
public function modifyLdapEntry($dn, array $attributes = [], $oldAttributes = FALSE) {
$this
->connectAndBindIfNotAlready();
if (!$oldAttributes) {
$result = @ldap_read($this->connection, $dn, 'objectClass=*');
if (!$result) {
$this->logger
->error("LDAP Server ldap_read(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %id, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ", [
'%dn' => $dn,
'%id' => $this
->id(),
'%ldap_errno' => ldap_errno($this->connection),
'%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)),
]);
return FALSE;
}
$entries = ldap_get_entries($this->connection, $result);
if (is_array($entries) && $entries['count'] == 1) {
$oldAttributes = $entries[0];
}
}
if (!empty($attributes['unicodePwd']) && $this
->get('type') == 'ad') {
$attributes['unicodePwd'] = $this
->convertPasswordForActiveDirectoryunicodePwd($attributes['unicodePwd']);
}
$attributes = $this
->removeUnchangedAttributes($attributes, $oldAttributes);
foreach ($attributes as $key => $currentValue) {
$oldValue = FALSE;
$keyLowercased = mb_strtolower($key);
if (isset($oldAttributes[$keyLowercased])) {
if ($oldAttributes[$keyLowercased]['count'] == 1) {
$oldValue = $oldAttributes[$keyLowercased][0];
}
else {
unset($oldAttributes[$keyLowercased]['count']);
$oldValue = $oldAttributes[$keyLowercased];
}
}
if ($currentValue == '' && $oldValue != '') {
unset($attributes[$key]);
$result = @ldap_mod_del($this->connection, $dn, [
$keyLowercased => $oldValue,
]);
if (!$result) {
$this->logger
->error("LDAP Server ldap_mod_del(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %id, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ", [
'%dn' => $dn,
'%id' => $this
->id(),
'%ldap_errno' => ldap_errno($this->connection),
'%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)),
]);
return FALSE;
}
}
elseif (is_array($currentValue)) {
foreach ($currentValue as $nestedKey => $nestedValue) {
if ($nestedValue == '') {
unset($attributes[$key][$nestedKey]);
}
else {
$attributes[$key][$nestedKey] = $nestedValue;
}
}
}
}
if (count($attributes) > 0) {
$result = @ldap_modify($this->connection, $dn, $attributes);
if (!$result) {
$this->logger
->error("LDAP Server ldap_modify(%dn) in LdapServer::modifyLdapEntry() Error Server ID = %id, LDAP Err No: %ldap_errno LDAP Err Message: %ldap_err2str ", [
'%dn' => $dn,
'%id' => $this
->id(),
'%ldap_errno' => ldap_errno($this->connection),
'%ldap_err2str' => ldap_err2str(ldap_errno($this->connection)),
]);
return FALSE;
}
}
return TRUE;
}
public function searchAllBaseDns($filter, array $attributes = [], $scope = NULL) {
if ($scope == NULL) {
$scope = Server::SCOPE_SUBTREE;
}
$allEntries = [];
foreach ($this
->getBaseDn() as $baseDn) {
$relativeFilter = str_replace(',' . $baseDn, '', $filter);
$entries = $this
->search($baseDn, $relativeFilter, $attributes, 0, 0, 0, NULL, $scope);
if ($entries === FALSE) {
return FALSE;
}
if (count($allEntries) == 0) {
$allEntries = $entries;
}
else {
$existingCount = $allEntries['count'];
unset($entries['count']);
foreach ($entries as $i => $entry) {
$allEntries[$existingCount + $i] = $entry;
}
$allEntries['count'] = count($allEntries);
}
}
return $allEntries;
}
public function search($base_dn = NULL, $filter, array $attributes = [], $attrsonly = 0, $sizelimit = 0, $timelimit = 0, $deref = NULL, $scope = NULL) {
if ($scope == NULL) {
$scope = Server::SCOPE_SUBTREE;
}
if ($base_dn == NULL) {
if (count($this
->getBaseDn()) == 1) {
$base_dn = $this
->getBaseDn()[0];
}
else {
return FALSE;
}
}
$this->detailLog
->log("LDAP search call with base_dn '%base_dn'. Filter is '%filter' with attributes '%attributes'. Only attributes %attrs_only, size limit %size_limit, time limit %time_limit, dereference %deref, scope %scope.", [
'%base_dn' => $base_dn,
'%filter' => $filter,
'%attributes' => is_array($attributes) ? implode(',', $attributes) : 'none',
'%attrs_only' => $attrsonly,
'%size_limit' => $sizelimit,
'%time_limit' => $timelimit,
'%deref' => $deref ? $deref : 'null',
'%scope' => $scope ? $scope : 'null',
]);
$this
->connectAndBindIfNotAlready();
$ldapQueryParams = [
'connection' => $this->connection,
'base_dn' => $base_dn,
'filter' => $filter,
'attributes' => $attributes,
'attrsonly' => $attrsonly,
'sizelimit' => $sizelimit,
'timelimit' => $timelimit,
'deref' => $deref,
'scope' => $scope,
];
if ($this
->get('search_pagination')) {
$aggregated_entries = $this
->pagedLdapQuery($ldapQueryParams);
return $aggregated_entries;
}
else {
$result = $this
->ldapQuery($scope, $ldapQueryParams);
if ($result && $this
->countEntries($result) !== FALSE) {
$entries = ldap_get_entries($this->connection, $result);
\Drupal::moduleHandler()
->alter('ldap_server_search_results', $entries, $ldapQueryParams);
return is_array($entries) ? $entries : FALSE;
}
elseif ($this
->hasError()) {
$this->logger
->notice("LDAP search error: %error. Context is base DN: %base_dn | filter: %filter| attributes: %attributes", [
'%base_dn' => $ldapQueryParams['base_dn'],
'%filter' => $ldapQueryParams['filter'],
'%attributes' => json_encode($ldapQueryParams['attributes']),
'%error' => $this
->formattedError($this
->ldapErrorNumber()),
]);
return FALSE;
}
else {
return FALSE;
}
}
}
public function pagedLdapQuery($queryParameters) {
if (!$this
->get('search_pagination')) {
$this->logger
->error('Paged LDAP query functionality called but not enabled in LDAP server configuration.');
return FALSE;
}
$pageToken = '';
$page = 0;
$estimatedEntries = 0;
$aggregatedEntries = [];
$aggregatedEntriesCount = 0;
$hasPageResults = FALSE;
do {
ldap_control_paged_result($this->connection, $this
->get('search_page_size'), TRUE, $pageToken);
$result = $this
->ldapQuery($queryParameters['scope'], $queryParameters);
if ($page >= $this->searchPageStart) {
$skippedPage = FALSE;
if ($result && $this
->countEntries($result) !== FALSE) {
$pageEntries = ldap_get_entries($this->connection, $result);
unset($pageEntries['count']);
$hasPageResults = is_array($pageEntries) && count($pageEntries) > 0;
$aggregatedEntries = array_merge($aggregatedEntries, $pageEntries);
$aggregatedEntriesCount = count($aggregatedEntries);
}
elseif ($this
->hasError()) {
$this->logger
->notice('LDAP search error: %error. Base DN: %base_dn | filter: %filter | attributes: %attributes.', [
'%base_dn' => $queryParameters['base_dn'],
'%filter' => $queryParameters['filter'],
'%attributes' => json_encode($queryParameters['attributes']),
'%error' => $this
->formattedError($this
->ldapErrorNumber()),
]);
return FALSE;
}
else {
return FALSE;
}
}
else {
$skippedPage = TRUE;
}
@ldap_control_paged_result_response($this->connection, $result, $pageToken, $estimatedEntries);
if ($queryParameters['sizelimit'] && $this
->ldapErrorNumber() == self::LDAP_SIZELIMIT_EXCEEDED) {
}
elseif ($this
->hasError()) {
$this->logger
->error('Paged query error: %error. Base DN: %base_dn | filter: %filter | attributes: %attributes.', [
'%error' => $this
->formattedError($this
->ldapErrorNumber()),
'%base_dn' => $queryParameters['base_dn'],
'%filter' => $queryParameters['filter'],
'%attributes' => json_encode($queryParameters['attributes']),
'%query' => $queryParameters['query_display'],
]);
}
if (isset($queryParameters['sizelimit']) && $queryParameters['sizelimit'] && $aggregatedEntriesCount >= $queryParameters['sizelimit']) {
$discarded_entries = array_splice($aggregatedEntries, $queryParameters['sizelimit']);
break;
}
elseif ($this->searchPageEnd !== NULL && $page >= $this->searchPageEnd) {
break;
}
elseif ($pageToken === NULL || $pageToken == '') {
break;
}
$page++;
} while ($skippedPage || $hasPageResults);
$aggregatedEntries['count'] = count($aggregatedEntries);
return $aggregatedEntries;
}
public function ldapQuery($scope, array $params) {
$result = FALSE;
$this
->connectAndBindIfNotAlready();
switch ($scope) {
case Server::SCOPE_SUBTREE:
$result = @ldap_search($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], $params['sizelimit'], $params['timelimit'], $params['deref']);
if ($params['sizelimit'] && $this
->ldapErrorNumber() == self::LDAP_SIZELIMIT_EXCEEDED) {
}
elseif ($this
->hasError()) {
$this->logger
->error('ldap_search() function error. LDAP Error: %message, ldap_search() parameters: %query', [
'%message' => $this
->formattedError($this
->ldapErrorNumber()),
'%query' => isset($params['query_display']) ? $params['query_display'] : NULL,
]);
}
break;
case Server::SCOPE_BASE:
$result = @ldap_read($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], $params['sizelimit'], $params['timelimit'], $params['deref']);
if ($params['sizelimit'] && $this
->ldapErrorNumber() == self::LDAP_SIZELIMIT_EXCEEDED) {
}
elseif ($this
->hasError()) {
$this->logger
->error('ldap_read() function error. LDAP Error: %message, ldap_read() parameters: %query', [
'%message' => $this
->formattedError($this
->ldapErrorNumber()),
'%query' => @$params['query_display'],
]);
}
break;
case Server::SCOPE_ONE_LEVEL:
$result = @ldap_list($this->connection, $params['base_dn'], $params['filter'], $params['attributes'], $params['attrsonly'], $params['sizelimit'], $params['timelimit'], $params['deref']);
if ($params['sizelimit'] && $this
->ldapErrorNumber() == self::LDAP_SIZELIMIT_EXCEEDED) {
}
elseif ($this
->hasError()) {
$this->logger
->error('ldap_list() function error. LDAP Error: %message, ldap_list() parameters: %query', [
'%message' => $this
->formattedError($this
->ldapErrorNumber()),
'%query' => $params['query_display'],
]);
}
break;
}
return $result;
}
public function dnArrayToLowerCase(array $dns) {
return array_keys(array_change_key_case(array_flip($dns), CASE_LOWER));
}
public function getBaseDn() {
$baseDn = $this
->get('basedn');
if (!is_array($baseDn) && is_scalar($baseDn)) {
$baseDn = explode("\r\n", $baseDn);
}
return $baseDn;
}
public function userAccountFromPuid($puid) {
$query = \Drupal::entityQuery('user');
$query
->condition('ldap_user_puid_sid', $this
->id(), '=')
->condition('ldap_user_puid', $puid, '=')
->condition('ldap_user_puid_property', $this
->get('unique_persistent_attr'), '=')
->accessCheck(FALSE);
$result = $query
->execute();
if (!empty($result)) {
if (count($result) == 1) {
return User::load(array_values($result)[0]);
}
else {
$uids = implode(',', $result);
$this->logger
->error('Multiple users (uids: %uids) with same puid (puid=%puid, sid=%sid, ldap_user_puid_property=%ldap_user_puid_property)', [
'%uids' => $uids,
'%puid' => $puid,
'%id' => $this
->id(),
'%ldap_user_puid_property' => $this
->get('unique_persistent_attr'),
]);
return FALSE;
}
}
else {
return FALSE;
}
}
public function userUsernameFromLdapEntry(array $ldap_entry) {
if ($this
->get('account_name_attr')) {
$accountName = empty($ldap_entry[$this
->get('account_name_attr')][0]) ? FALSE : $ldap_entry[$this
->get('account_name_attr')][0];
}
elseif ($this
->get('user_attr')) {
$accountName = empty($ldap_entry[$this
->get('user_attr')][0]) ? FALSE : $ldap_entry[$this
->get('user_attr')][0];
}
else {
$accountName = FALSE;
}
return $accountName;
}
public function userEmailFromLdapEntry(array $ldapEntry) {
if ($ldapEntry && $this
->get('mail_attr') && isset($ldapEntry[$this
->get('mail_attr')][0])) {
$mail = isset($ldapEntry[$this
->get('mail_attr')][0]) ? $ldapEntry[$this
->get('mail_attr')][0] : FALSE;
return $mail;
}
elseif ($ldapEntry && $this
->get('mail_template')) {
return $this->tokenProcessor
->tokenReplace($ldapEntry, $this
->get('mail_template'), 'ldap_entry');
}
else {
return FALSE;
}
}
public function userPuidFromLdapEntry(array $ldapEntry) {
if ($this
->get('unique_persistent_attr') && isset($ldapEntry[mb_strtolower($this
->get('unique_persistent_attr'))])) {
$puid = $ldapEntry[mb_strtolower($this
->get('unique_persistent_attr'))];
if (is_array($puid)) {
$puid = $puid[0];
}
return $this
->get('unique_persistent_attr_binary') ? ConversionHelper::binaryConversionToString($puid) : $puid;
}
else {
return FALSE;
}
}
public function userUserToExistingLdapEntry($user) {
$userLdapEntry = FALSE;
if (is_object($user)) {
$userLdapEntry = $this
->matchUsernameToExistingLdapEntry($user
->getAccountName());
}
elseif (is_array($user)) {
$userLdapEntry = $user;
}
elseif (is_scalar($user)) {
if (strpos($user, '=') === FALSE) {
$userLdapEntry = $this
->matchUsernameToExistingLdapEntry($user);
}
else {
$userLdapEntry = $this
->checkDnExistsIncludeData($user, [
'objectclass',
]);
}
}
return $userLdapEntry;
}
public function matchUsernameToExistingLdapEntry($drupalUsername) {
foreach ($this
->getBaseDn() as $baseDn) {
if (empty($baseDn)) {
continue;
}
$massager = new MassageAttributes();
$filter = '(' . $this
->get('user_attr') . '=' . $massager
->queryLdapAttributeValue($drupalUsername) . ')';
$result = $this
->search($baseDn, $filter);
if (!$result || !isset($result['count']) || !$result['count']) {
continue;
}
if ($result['count'] != 1) {
$count = $result['count'];
$this->logger
->error('Error: %count users found with %filter under %base_dn.', [
'%count' => $count,
'%filter' => $filter,
'%base_dn' => $baseDn,
]);
continue;
}
$match = $result[0];
$nameAttribute = $this
->get('user_attr');
if (isset($match[$nameAttribute][0])) {
}
elseif (isset($match[mb_strtolower($nameAttribute)][0])) {
$nameAttribute = mb_strtolower($nameAttribute);
}
else {
if ($this
->get('bind_method') == 'anon_user') {
$result = [
'dn' => $match['dn'],
'mail' => $this
->userEmailFromLdapEntry($match),
'attr' => $match,
'id' => $this
->id(),
];
return $result;
}
else {
continue;
}
}
foreach ($match[$nameAttribute] as $value) {
if (mb_strtolower(trim($value)) == mb_strtolower($drupalUsername)) {
$result = [
'dn' => $match['dn'],
'mail' => $this
->userEmailFromLdapEntry($match),
'attr' => $match,
'id' => $this
->id(),
];
return $result;
}
}
}
}
public function groupIsMember($groupDn, $user) {
$groupDns = $this
->groupMembershipsFromUser($user);
if (is_array($groupDns) && in_array(mb_strtolower($groupDn), $this
->dnArrayToLowerCase($groupDns))) {
return TRUE;
}
else {
return FALSE;
}
}
public function groupMembersRecursive(array $group_dn_entries, array &$all_member_dns, array $tested_group_dns, $level, $max_levels, $object_classes = FALSE) {
if (!$this
->groupGroupEntryMembershipsConfigured() || !is_array($group_dn_entries)) {
return FALSE;
}
if (isset($group_dn_entries['count'])) {
unset($group_dn_entries['count']);
}
foreach ($group_dn_entries as $member_entry) {
$object_class_match = !$object_classes || count(array_intersect(array_values($member_entry['objectclass']), $object_classes)) > 0;
$object_is_group = in_array($this
->groupObjectClass(), array_map('strtolower', array_values($member_entry['objectclass'])));
if ($object_class_match && !in_array($member_entry['dn'], $all_member_dns)) {
$all_member_dns[] = $member_entry['dn'];
}
if ($object_is_group && $level < $max_levels) {
if ($this
->groupMembershipsAttrMatchingUserAttr() == 'dn') {
$group_id = $member_entry['dn'];
}
else {
$group_id = $member_entry[$this
->groupMembershipsAttrMatchingUserAttr()][0];
}
if (!in_array($group_id, $tested_group_dns)) {
$tested_group_dns[] = $group_id;
$member_ids = $member_entry[$this
->groupMembershipsAttr()];
if (isset($member_ids['count'])) {
unset($member_ids['count']);
}
if (count($member_ids)) {
$query_for_child_members = '(|(' . implode(")(", $member_ids) . '))';
if ($object_classes && count($object_classes)) {
$object_classes_ors = [
'(objectClass=' . $this
->groupObjectClass() . ')',
];
foreach ($object_classes as $object_class) {
$object_classes_ors[] = '(objectClass=' . $object_class . ')';
}
$query_for_child_members = '&(|' . implode($object_classes_ors) . ')(' . $query_for_child_members . ')';
}
$return_attributes = [
'objectclass',
$this
->groupMembershipsAttr(),
$this
->groupMembershipsAttrMatchingUserAttr(),
];
$child_member_entries = $this
->searchAllBaseDns($query_for_child_members, $return_attributes);
if ($child_member_entries !== FALSE) {
$this
->groupMembersRecursive($child_member_entries, $all_member_dns, $tested_group_dns, $level + 1, $max_levels, $object_classes);
}
}
}
}
}
}
public function groupMembershipsFromUser($user) {
$group_dns = FALSE;
$user_ldap_entry = @$this
->userUserToExistingLdapEntry($user);
if (!$user_ldap_entry || $this
->groupFunctionalityUnused()) {
return FALSE;
}
if ($this
->groupUserMembershipsFromAttributeConfigured()) {
$group_dns = $this
->groupUserMembershipsFromUserAttr($user_ldap_entry);
}
elseif ($this
->groupGroupEntryMembershipsConfigured()) {
$group_dns = $this
->groupUserMembershipsFromEntry($user_ldap_entry);
}
return $group_dns;
}
public function groupUserMembershipsFromUserAttr($user) {
if (!$this
->groupUserMembershipsFromAttributeConfigured()) {
return FALSE;
}
$groupAttribute = $this
->groupUserMembershipsAttr();
if (empty($user['attr'][$groupAttribute])) {
$user = $this
->userUserToExistingLdapEntry($user);
if (empty($user['attr'][$groupAttribute])) {
return FALSE;
}
}
$userLdapEntry = $user;
$allGroupDns = [];
$level = 0;
$membersGroupDns = $userLdapEntry['attr'][$groupAttribute];
if (isset($membersGroupDns['count'])) {
unset($membersGroupDns['count']);
}
$orFilters = [];
foreach ($membersGroupDns as $memberGroupDn) {
$allGroupDns[] = $memberGroupDn;
if ($this
->groupNested()) {
if ($this
->groupMembershipsAttrMatchingUserAttr() == 'dn') {
$member_value = $memberGroupDn;
}
else {
$member_value = $this
->getFirstRdnValueFromDn($memberGroupDn, $this
->groupMembershipsAttrMatchingUserAttr());
}
$orFilters[] = $this
->groupMembershipsAttr() . '=' . self::ldapEscape($member_value);
}
}
if ($this
->groupNested() && count($orFilters)) {
$allGroupDns = $this
->getNestedGroupDnFilters($allGroupDns, $orFilters, $level);
}
return $allGroupDns;
}
public function groupUserMembershipsFromEntry($user) {
if (!$this
->groupGroupEntryMembershipsConfigured()) {
return FALSE;
}
$userLdapEntry = $this
->userUserToExistingLdapEntry($user);
$allGroupDns = [];
$testedGroupIds = [];
$level = 0;
if ($this
->groupMembershipsAttrMatchingUserAttr() == 'dn') {
$member_value = $userLdapEntry['dn'];
}
else {
$member_value = $userLdapEntry['attr'][$this
->groupMembershipsAttrMatchingUserAttr()][0];
}
$groupQuery = '(&(objectClass=' . $this
->groupObjectClass() . ')(' . $this
->groupMembershipsAttr() . "={$member_value}))";
foreach ($this
->getBaseDn() as $baseDn) {
$groupEntries = $this
->search($baseDn, $groupQuery, []);
if ($groupEntries !== FALSE) {
$maxLevels = $this
->groupNested() ? self::LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT : 0;
$this
->groupMembershipsFromEntryRecursive($groupEntries, $allGroupDns, $testedGroupIds, $level, $maxLevels);
}
}
return $allGroupDns;
}
private function groupMembershipsFromEntryRecursive(array $currentGroupEntries, array &$allGroupDns, array &$testedGroupIds, $level, $maxLevels) {
if (!$this
->groupGroupEntryMembershipsConfigured() || !is_array($currentGroupEntries) || count($currentGroupEntries) == 0) {
return FALSE;
}
if (isset($currentGroupEntries['count'])) {
unset($currentGroupEntries['count']);
}
$orFilters = [];
foreach ($currentGroupEntries as $key => $groupEntry) {
if ($this
->groupMembershipsAttrMatchingUserAttr() == 'dn') {
$memberId = $groupEntry['dn'];
}
else {
$memberId = $this
->getFirstRdnValueFromDn($groupEntry['dn'], $this
->groupMembershipsAttrMatchingUserAttr());
}
if ($memberId && !in_array($memberId, $testedGroupIds)) {
$testedGroupIds[] = $memberId;
$allGroupDns[] = $groupEntry['dn'];
$orFilters[] = $this
->groupMembershipsAttr() . '=' . self::ldapEscape($memberId);
}
}
if (count($orFilters)) {
for ($key = 0; $key < count($orFilters); $key = $key + self::LDAP_SERVER_LDAP_QUERY_CHUNK) {
$currentOrFilters = array_slice($orFilters, $key, self::LDAP_SERVER_LDAP_QUERY_CHUNK);
$or = '(|(' . implode(")(", $currentOrFilters) . '))';
$queryForParentGroups = '(&(objectClass=' . $this
->groupObjectClass() . ')' . $or . ')';
foreach ($this
->getBaseDn() as $baseDn) {
$group_entries = $this
->search($baseDn, $queryForParentGroups);
if ($group_entries !== FALSE && $level < $maxLevels) {
$this
->groupMembershipsFromEntryRecursive($group_entries, $allGroupDns, $testedGroupIds, $level + 1, $maxLevels);
}
}
}
}
return TRUE;
}
public function groupUserMembershipsFromDn($user) {
if (!$this
->groupDeriveFromDn() || !$this
->groupDeriveFromDnAttr()) {
return FALSE;
}
elseif ($user_ldap_entry = $this
->userUserToExistingLdapEntry($user)) {
return $this
->getAllRdnValuesFromDn($user_ldap_entry['dn'], $this
->groupDeriveFromDnAttr());
}
else {
return FALSE;
}
}
public function hasError() {
if ($this
->ldapErrorNumber() != Server::LDAP_SUCCESS) {
return TRUE;
}
else {
return FALSE;
}
}
public function formattedError($number) {
return ldap_err2str($number) . ' (' . $number . ')';
}
public function ldapErrorNumber() {
return ldap_errno($this->connection);
}
protected function groupFunctionalityUnused() {
return $this
->get('grp_unused');
}
protected function groupNested() {
return $this
->get('grp_nested');
}
protected function groupUserMembershipsAttrExists() {
return $this
->get('grp_user_memb_attr_exists');
}
protected function groupUserMembershipsAttr() {
return $this
->get('grp_user_memb_attr');
}
protected function groupMembershipsAttrMatchingUserAttr() {
return $this
->get('grp_memb_attr_match_user_attr');
}
public function groupMembershipsAttr() {
return $this
->get('grp_memb_attr');
}
public function groupObjectClass() {
return $this
->get('grp_object_cat');
}
protected function groupDeriveFromDn() {
return $this
->get('grp_derive_from_dn');
}
protected function groupDeriveFromDnAttr() {
return $this
->get('grp_derive_from_dn_attr');
}
public function groupUserMembershipsFromAttributeConfigured() {
return $this
->groupUserMembershipsAttrExists() && $this
->groupUserMembershipsAttr();
}
public function groupGroupEntryMembershipsConfigured() {
return $this
->groupMembershipsAttrMatchingUserAttr() && $this
->groupMembershipsAttr();
}
private function getFirstRdnValueFromDn($dn, $rdn) {
$pairs = $this
->ldapExplodeDn($dn, 0);
array_shift($pairs);
$rdn = mb_strtolower($rdn);
$rdn_value = FALSE;
foreach ($pairs as $p) {
$pair = explode('=', $p);
if (mb_strtolower(trim($pair[0])) == $rdn) {
$rdn_value = ConversionHelper::unescapeDnValue(trim($pair[1]));
break;
}
}
return $rdn_value;
}
private function getAllRdnValuesFromDn($dn, $rdn) {
$pairs = $this
->ldapExplodeDn($dn, 0);
array_shift($pairs);
$rdn = mb_strtolower($rdn);
$rdn_values = [];
foreach ($pairs as $p) {
$pair = explode('=', $p);
if (mb_strtolower(trim($pair[0])) == $rdn) {
$rdn_values[] = ConversionHelper::unescapeDnValue(trim($pair[1]));
break;
}
}
return $rdn_values;
}
public static function ldapExplodeDn($dn, $attribute) {
return ldap_explode_dn($dn, $attribute);
}
public function convertPasswordForActiveDirectoryunicodePwd($password) {
if (!is_array($password)) {
return mb_convert_encoding("\"{$password}\"", "UTF-16LE");
}
else {
return [
mb_convert_encoding("\"{$password[0]}\"", "UTF-16LE"),
];
}
}
private function getNestedGroupDnFilters(array $allGroupDns, array $orFilters, $level) {
for ($key = 0; $key < count($orFilters); $key = $key + self::LDAP_SERVER_LDAP_QUERY_CHUNK) {
$currentOrFilters = array_slice($orFilters, $key, self::LDAP_SERVER_LDAP_QUERY_CHUNK);
$orFilter = '(|(' . implode(")(", $currentOrFilters) . '))';
$queryForParentGroups = '(&(objectClass=' . $this
->groupObjectClass() . ')' . $orFilter . ')';
foreach ($this
->getBaseDn() as $basedn) {
$groupEntries = $this
->search($basedn, $queryForParentGroups);
if ($groupEntries !== FALSE && $level < self::LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT) {
$testedGroupIds = [];
$this
->groupMembershipsFromEntryRecursive($groupEntries, $allGroupDns, $testedGroupIds, $level + 1, self::LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT);
}
}
}
return $allGroupDns;
}
public function groupAddGroup($group_dn, array $attributes = []) {
if ($this
->checkDnExists($group_dn)) {
return FALSE;
}
$attributes = array_change_key_case($attributes, CASE_LOWER);
if (empty($attributes['objectclass'])) {
$objectClass = $this
->groupObjectClass();
}
else {
$objectClass = $attributes['objectclass'];
}
$attributes['objectclass'] = $objectClass;
$context = [
'action' => 'add',
'corresponding_drupal_data' => [
$group_dn => $attributes,
],
'corresponding_drupal_data_type' => 'group',
];
$ldap_entries = [
$group_dn => $attributes,
];
$this->moduleHandler
->alter('ldap_entry_pre_provision', $ldap_entries, $this, $context);
$attributes = $ldap_entries[$group_dn];
$ldap_entry_created = $this
->createLdapEntry($attributes, $group_dn);
if ($ldap_entry_created) {
$this->moduleHandler
->invokeAll('ldap_entry_post_provision', [
$ldap_entries,
$this,
$context,
]);
return TRUE;
}
else {
return FALSE;
}
}
public function groupRemoveGroup($group_dn, $only_if_group_empty = TRUE) {
if ($only_if_group_empty) {
$members = $this
->groupAllMembers($group_dn);
if (is_array($members) && count($members) > 0) {
return FALSE;
}
}
return $this
->deleteLdapEntry($group_dn);
}
public function groupAddMember($group_dn, $user) {
$result = FALSE;
if ($this
->groupGroupEntryMembershipsConfigured()) {
$this
->connectAndBindIfNotAlready();
$new_member = [
$this
->groupMembershipsAttr() => $user,
];
$result = @ldap_mod_add($this->connection, $group_dn, $new_member);
}
return $result;
}
public function groupRemoveMember($group_dn, $member) {
$result = FALSE;
if ($this
->groupGroupEntryMembershipsConfigured()) {
$del = [];
$del[$this
->groupMembershipsAttr()] = $member;
$this
->connectAndBindIfNotAlready();
$result = @ldap_mod_del($this->connection, $group_dn, $del);
}
return $result;
}
public function groupAllMembers($group_dn) {
if (!$this
->groupGroupEntryMembershipsConfigured()) {
return FALSE;
}
$attributes = [
$this
->groupMembershipsAttr(),
'cn',
'objectclass',
];
$group_entry = $this
->checkDnExistsIncludeData($group_dn, $attributes);
if (!$group_entry) {
return FALSE;
}
else {
if (empty($group_entry['cn'])) {
return FALSE;
}
if (empty($group_entry[$this
->groupMembershipsAttr()])) {
return [];
}
$members = $group_entry[$this
->groupMembershipsAttr()];
if (isset($members['count'])) {
unset($members['count']);
}
$result = $this
->groupMembersRecursive([
$group_entry,
], $members, [], 0, self::LDAP_SERVER_LDAP_QUERY_RECURSION_LIMIT);
if (($key = array_search($group_dn, $members)) !== FALSE) {
unset($members[$key]);
}
}
if ($result !== FALSE) {
return $members;
}
else {
return FALSE;
}
}
public function groupMembers($group_dn) {
if (!$this
->groupGroupEntryMembershipsConfigured()) {
return FALSE;
}
$attributes = [
$this
->groupMembershipsAttr(),
'cn',
'objectclass',
];
$group_entry = $this
->checkDnExistsIncludeData($group_dn, $attributes);
if (!$group_entry) {
return FALSE;
}
else {
if (empty($group_entry['cn'])) {
return FALSE;
}
if (empty($group_entry[$this
->groupMembershipsAttr()])) {
return [];
}
$members = $group_entry[$this
->groupMembershipsAttr()];
if (isset($members['count'])) {
unset($members['count']);
}
return $members;
}
}
}