View source
<?php
declare (strict_types=1);
namespace Drupal\ldap_servers;
use Drupal\ldap_servers\Helper\ConversionHelper;
use Symfony\Component\Ldap\Adapter\CollectionInterface;
use Symfony\Component\Ldap\Entry;
use Symfony\Component\Ldap\Exception\LdapException;
use function in_array;
class LdapGroupManager extends LdapBaseManager {
use LdapTransformationTraits;
protected const LDAP_QUERY_RECURSION_LIMIT = 10;
public function groupGroupEntryMembershipsConfigured() : bool {
return $this->server
->get('grp_memb_attr_match_user_attr') && $this->server
->get('grp_memb_attr');
}
private function getNestedGroupDnFilters(array $all_group_dns, array $or_filters, int $level) : array {
$or_filter = sprintf('(|(%s))', implode(')(', $or_filters));
$query_for_parent_groups = sprintf('(&(objectClass=%s)%s)', $this->server
->get('grp_object_cat'), $or_filter);
foreach ($this->server
->getBaseDn() as $base_dn) {
try {
$ldap_result = $this->ldap
->query($base_dn, $query_for_parent_groups, [
'filter' => [],
])
->execute();
} catch (LdapException $e) {
$this->logger
->critical('LDAP search error with %message', [
'%message' => $e
->getMessage(),
]);
continue;
}
if ($level < self::LDAP_QUERY_RECURSION_LIMIT && $ldap_result
->count() > 0) {
$tested_group_ids = [];
$this
->groupMembershipsFromEntryRecursive($ldap_result, $all_group_dns, $tested_group_ids, $level + 1, self::LDAP_QUERY_RECURSION_LIMIT);
}
}
return $all_group_dns;
}
public function groupAddGroup(string $group_dn, array $attributes = []) : bool {
if (!$this
->checkAvailability() || $this
->checkDnExists($group_dn)) {
return FALSE;
}
$attributes = array_change_key_case($attributes, CASE_LOWER);
if (empty($attributes['objectclass'])) {
$objectClass = $this->server
->get('grp_object_cat');
}
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];
$entry = new Entry($group_dn, $attributes);
try {
$this->ldap
->getEntryManager()
->add($entry);
} catch (LdapException $e) {
$this->logger
->error('LDAP server %id exception: %ldap_error', [
'%id' => $this->server
->id(),
'%ldap_error' => $e
->getMessage(),
]);
return FALSE;
}
$this->moduleHandler
->invokeAll('ldap_entry_post_provision', [
$ldap_entries,
$this,
$context,
]);
return TRUE;
}
public function groupRemoveGroup(string $group_dn, bool $only_if_group_empty = TRUE) : bool {
if (!$this
->checkAvailability()) {
return FALSE;
}
if ($only_if_group_empty) {
$members = $this
->groupAllMembers($group_dn);
if (!empty($members)) {
return FALSE;
}
}
return $this
->deleteLdapEntry($group_dn);
}
public function groupAddMember(string $group_dn, string $user) : bool {
if (!$this
->checkAvailability()) {
return FALSE;
}
$result = FALSE;
if ($this
->groupGroupEntryMembershipsConfigured()) {
$entry = new Entry($group_dn);
$manager = $this->ldap
->getEntryManager();
try {
$manager
->addAttributeValues($entry, $this->server
->get('grp_memb_attr'), [
$user,
]);
$result = TRUE;
} catch (LdapException $e) {
$this->logger
->error('LDAP server error updating %dn on @sid exception: %ldap_error', [
'%dn' => $group_dn,
'@sid' => $this->server
->id(),
'%ldap_error' => $e
->getMessage(),
]);
}
}
return $result;
}
public function groupRemoveMember(string $group_dn, string $member) : bool {
$result = FALSE;
if ($this
->checkAvailability() && $this
->groupGroupEntryMembershipsConfigured()) {
$entry = new Entry($group_dn);
$manager = $this->ldap
->getEntryManager();
try {
$manager
->removeAttributeValues($entry, $this->server
->get('grp_memb_attr'), [
$member,
]);
$result = TRUE;
} catch (LdapException $e) {
$this->logger
->error('LDAP server error updating %dn on @sid exception: %ldap_error', [
'%dn' => $group_dn,
'@sid' => $this->server
->id(),
'%ldap_error' => $e
->getMessage(),
]);
}
}
return $result;
}
public function groupAllMembers(string $group_dn) : array {
$members = [];
if (!$this
->checkAvailability() || !$this
->groupGroupEntryMembershipsConfigured()) {
return $members;
}
$attributes = [
$this->server
->get('grp_memb_attr'),
'cn',
'objectclass',
];
$group_entry = $this
->checkDnExistsIncludeData($group_dn, $attributes);
if (!$group_entry) {
return $members;
}
if (empty($group_entry
->getAttribute('cn', FALSE)) || empty($group_entry
->getAttribute($this->server
->get('grp_memb_attr'), FALSE))) {
return $members;
}
$members = $group_entry
->getAttribute($this->server
->get('grp_memb_attr'), FALSE);
$this
->groupMembersRecursive([
$group_entry,
], $members, [], 0, self::LDAP_QUERY_RECURSION_LIMIT);
$source_dn_key = array_search($group_dn, $members, TRUE);
if ($source_dn_key !== FALSE) {
unset($members[$source_dn_key]);
}
return $members;
}
public function groupMembers(string $group_dn) {
if (!$this
->checkAvailability()) {
return FALSE;
}
if (!$this
->groupGroupEntryMembershipsConfigured()) {
return FALSE;
}
$attributes = [
$this->server
->get('grp_memb_attr'),
'cn',
'objectclass',
];
$group_entry = $this
->checkDnExistsIncludeData($group_dn, $attributes);
if (!$group_entry) {
return FALSE;
}
if (!$group_entry
->hasAttribute('cn', FALSE)) {
return FALSE;
}
if (!$group_entry
->hasAttribute($this->server
->get('grp_memb_attr'), FALSE)) {
return [];
}
return $group_entry
->getAttribute($this->server
->get('grp_memb_attr'), FALSE);
}
public function groupIsMember(string $group_dn, string $username) : bool {
if ($this
->checkAvailability()) {
$group_dns = $this
->groupMembershipsFromUser($username);
if (!empty($group_dns)) {
$lower_cased_group_dns = array_keys(array_change_key_case(array_flip($group_dns), CASE_LOWER));
if (in_array(mb_strtolower($group_dn), $lower_cased_group_dns, TRUE)) {
return TRUE;
}
}
}
return FALSE;
}
public function groupMembersRecursive(array $entries, array &$all_member_dns, array $tested_group_dns, int $level, int $max_levels, ?array $object_classes = NULL) : void {
if (!$this
->checkAvailability()) {
return;
}
if (!$this
->groupGroupEntryMembershipsConfigured()) {
return;
}
foreach ($entries as $entry) {
$lowercased_object_class = array_map('strtolower', array_values($entry
->getAttribute('objectClass', FALSE)));
$object_is_group = in_array($this->server
->get('grp_object_cat'), $lowercased_object_class, TRUE);
$object_class_match = !$object_classes || count(array_intersect(array_values($entry
->getAttribute('objectClass', FALSE)), $object_classes)) > 0;
if ($object_class_match && !in_array($entry
->getDn(), $all_member_dns, TRUE)) {
$all_member_dns[] = $entry
->getDn();
}
if ($object_is_group && $level < $max_levels) {
if ($this->server
->get('grp_memb_attr_match_user_attr') === 'dn') {
$group_id = $entry
->getDn();
}
else {
$group_id = $entry
->getAttribute($this->server
->get('grp_memb_attr_match_user_attr'), FALSE)[0];
}
if (!in_array($group_id, $tested_group_dns, TRUE)) {
$tested_group_dns[] = $group_id;
$member_ids = $entry
->getAttribute($this->server
->get('grp_memb_attr'), FALSE);
if (count($member_ids)) {
$query_for_child_members = sprintf('(|(%s))', implode(')(', $member_ids));
if ($object_classes && count($object_classes)) {
$object_classes_ors = [
sprintf('(objectClass=%s)', $this->server
->get('grp_object_cat')),
];
foreach ($object_classes as $object_class) {
$object_classes_ors[] = sprintf('(objectClass=%s)', $object_class);
}
$query_for_child_members = sprintf('&(|%s)(%s)', implode('', $object_classes_ors), $query_for_child_members);
}
$child_member_entries = $this
->searchAllBaseDns($query_for_child_members, [
'objectClass',
$this->server
->get('grp_memb_attr'),
$this->server
->get('grp_memb_attr_match_user_attr'),
]);
if (!empty($child_member_entries)) {
$this
->groupMembersRecursive($child_member_entries, $all_member_dns, $tested_group_dns, $level + 1, $max_levels, $object_classes);
}
}
}
}
}
}
public function groupMembershipsFromUser(string $username) : array {
$group_dns = [];
if (!$this
->checkAvailability()) {
return $group_dns;
}
$user_ldap_entry = $this
->matchUsernameToExistingLdapEntry($username);
if (!$user_ldap_entry || $this->server
->get('grp_unused')) {
return $group_dns;
}
if ($this->server
->isGroupUserMembershipAttributeInUse() && $this->server
->getGroupUserMembershipAttribute()) {
$group_dns = $this
->groupUserMembershipsFromUserAttr($user_ldap_entry);
}
elseif ($this
->groupGroupEntryMembershipsConfigured()) {
$group_dns = $this
->groupUserMembershipsFromEntry($user_ldap_entry);
}
return $group_dns;
}
public function groupUserMembershipsFromUserAttr(Entry $ldap_entry) : array {
if (!$this
->checkAvailability() || !$this->server
->isGroupUserMembershipAttributeInUse()) {
return [];
}
$group_attribute = $this->server
->getGroupUserMembershipAttribute();
if (!$ldap_entry
->hasAttribute($group_attribute, FALSE)) {
return [];
}
$level = 0;
$all_group_dns = [];
$members_group_dns = $ldap_entry
->getAttribute($group_attribute, FALSE);
$orFilters = [];
foreach ($members_group_dns as $member_group_dn) {
$all_group_dns[] = $member_group_dn;
if ($this->server
->get('grp_nested')) {
if ($this->server
->get('grp_memb_attr_match_user_attr') === 'dn') {
$member_value = $member_group_dn;
}
else {
$member_value = $this
->getFirstRdnValueFromDn($member_group_dn);
}
$orFilters[] = $this->server
->get('grp_memb_attr') . '=' . $this
->ldapEscapeDn($member_value);
}
}
if ($this->server
->get('grp_nested') && count($orFilters)) {
$all_group_dns = $this
->getNestedGroupDnFilters($all_group_dns, $orFilters, $level);
}
return $all_group_dns;
}
public function groupUserMembershipsFromEntry(Entry $ldap_entry) : array {
$all_group_dns = [];
if (!$this
->checkAvailability() || !$this
->groupGroupEntryMembershipsConfigured()) {
return $all_group_dns;
}
$tested_group_ids = [];
$level = 0;
if ($this->server
->get('grp_memb_attr_match_user_attr') === 'dn') {
$member_value = $ldap_entry
->getDn();
}
else {
$member_value = $ldap_entry
->getAttribute($this->server
->get('grp_memb_attr_match_user_attr'), FALSE)[0];
}
foreach ($this->server
->getBaseDn() as $baseDn) {
try {
$group_query = sprintf('(&(objectClass=%s)(%s=%s))', $this->server
->get('grp_object_cat'), $this->server
->get('grp_memb_attr'), $member_value);
$ldap_result = $this->ldap
->query($baseDn, $group_query, [
'filter' => [],
])
->execute();
} catch (LdapException $e) {
$this->logger
->critical('LDAP search error with %message', [
'%message' => $e
->getMessage(),
]);
continue;
}
if ($ldap_result
->count() > 0) {
$maxLevels = $this->server
->get('grp_nested') ? self::LDAP_QUERY_RECURSION_LIMIT : 0;
$this
->groupMembershipsFromEntryRecursive($ldap_result, $all_group_dns, $tested_group_ids, $level, $maxLevels);
}
}
return $all_group_dns;
}
private function groupMembershipsFromEntryRecursive(CollectionInterface $current_group_entries, array &$all_group_dns, array &$tested_group_ids, int $level, int $max_levels) : bool {
if (!$this
->groupGroupEntryMembershipsConfigured() || $current_group_entries
->count() === 0) {
return FALSE;
}
$or_filters = [];
foreach ($current_group_entries
->toArray() as $group_entry) {
if ($this->server
->get('grp_memb_attr_match_user_attr') === 'dn') {
$member_id = $group_entry
->getDn();
}
else {
$member_id = $this
->getFirstRdnValueFromDn($group_entry
->getDn());
}
if ($member_id && !in_array($member_id, $tested_group_ids, TRUE)) {
$tested_group_ids[] = $member_id;
$all_group_dns[] = $group_entry
->getDn();
$or_filters[] = $this->server
->get('grp_memb_attr') . '=' . $this
->ldapEscapeDn($member_id);
}
}
if (!empty($or_filters)) {
$or = sprintf('(|(%s))', implode(')(', $or_filters));
$query_for_parent_groups = sprintf('(&(objectClass=%s)%s)', $this->server
->get('grp_object_cat'), $or);
foreach ($this->server
->getBaseDn() as $base_dn) {
try {
$ldap_result = $this->ldap
->query($base_dn, $query_for_parent_groups, [
'filter' => [],
])
->execute();
} catch (LdapException $e) {
$this->logger
->critical('LDAP search error with %message', [
'%message' => $e
->getMessage(),
]);
continue;
}
if ($level < $max_levels && $ldap_result
->count() > 0) {
$this
->groupMembershipsFromEntryRecursive($ldap_result, $all_group_dns, $tested_group_ids, $level + 1, $max_levels);
}
}
}
return TRUE;
}
public function groupUserMembershipsFromDn(string $username) : array {
$memberships = [];
if ($this
->checkAvailability() && $this->server
->isGroupDerivedFromDn() && $this->server
->getDerivedGroupFromDnAttribute()) {
$ldap_entry = $this
->matchUsernameToExistingLdapEntry($username);
if ($ldap_entry) {
$memberships = $this
->getAllRdnValuesFromDn($ldap_entry
->getDn(), $this->server
->getDerivedGroupFromDnAttribute());
}
}
return $memberships;
}
private function getFirstRdnValueFromDn(string $dn) : string {
$value = '';
if (!empty($dn)) {
$parts = self::splitDnWithValues($dn);
if ($parts && $parts['count'] > 0) {
$value = $parts[0];
$value = ConversionHelper::unescapeDnValue(trim($value));
}
}
return $value;
}
public function getAllRdnValuesFromDn(string $dn, string $rdn) : array {
$pairs = self::splitDnWithAttributes($dn);
array_shift($pairs);
$rdn = mb_strtolower($rdn);
$rdn_values = [];
foreach ($pairs as $p) {
$pair = explode('=', $p);
if ($pair !== FALSE && mb_strtolower(trim($pair[0])) === $rdn) {
$rdn_values[] = ConversionHelper::unescapeDnValue(trim($pair[1]));
break;
}
}
return $rdn_values;
}
}