Skip to content

Commit 551d904

Browse files
tofuSCHNITZELblizzz
andcommitted
added "zimbraMailForwardingAddress" as a Group-Member association attribute to enable the use of Zimbra Distribution Lists as groups in nextcloud when connecting to a zimbra LDAP
Signed-off-by: Tobias Perschon <[email protected]> fix cs:check Signed-off-by: Tobias Perschon <[email protected]> Update apps/user_ldap/lib/Group_LDAP.php Co-authored-by: blizzz <[email protected]> Signed-off-by: Tobias Perschon <[email protected]>
1 parent d12dd33 commit 551d904

File tree

3 files changed

+131
-85
lines changed

3 files changed

+131
-85
lines changed

apps/user_ldap/lib/Group_LDAP.php

Lines changed: 126 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* @author Roeland Jago Douma <[email protected]>
2121
* @author Roland Tapken <[email protected]>
2222
* @author Thomas Müller <[email protected]>
23+
* @author Tobias Perschon <[email protected]>
2324
* @author Victor Dubiniuk <[email protected]>
2425
* @author Vincent Petry <[email protected]>
2526
* @author Vinicius Cubas Brand <[email protected]>
@@ -66,6 +67,11 @@ class Group_LDAP extends BackendUtility implements GroupInterface, IGroupLDAP, I
6667
/** @var ILogger */
6768
protected $logger;
6869

70+
/**
71+
* @var string $ldapGroupMemberAssocAttr contains the LDAP setting (in lower case) with the same name
72+
*/
73+
protected $ldapGroupMemberAssocAttr;
74+
6975
public function __construct(Access $access, GroupPluginManager $groupPluginManager) {
7076
parent::__construct($access);
7177
$filter = $this->access->connection->ldapGroupFilter;
@@ -79,6 +85,7 @@ public function __construct(Access $access, GroupPluginManager $groupPluginManag
7985
$this->cachedNestedGroups = new CappedMemoryCache();
8086
$this->groupPluginManager = $groupPluginManager;
8187
$this->logger = OC::$server->getLogger();
88+
$this->ldapGroupMemberAssocAttr = strtolower($gAssoc);
8289
}
8390

8491
/**
@@ -136,31 +143,38 @@ public function inGroup($uid, $gid) {
136143
}
137144

138145
//extra work if we don't get back user DNs
139-
if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
140-
$requestAttributes = $this->access->userManager->getAttributes(true);
141-
$dns = [];
142-
$filterParts = [];
143-
$bytes = 0;
144-
foreach ($members as $mid) {
145-
$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
146-
$filterParts[] = $filter;
147-
$bytes += strlen($filter);
148-
if ($bytes >= 9000000) {
149-
// AD has a default input buffer of 10 MB, we do not want
150-
// to take even the chance to exceed it
146+
switch ($this->ldapGroupMemberAssocAttr) {
147+
case 'memberuid':
148+
case 'zimbramailforwardingaddress':
149+
$requestAttributes = $this->access->userManager->getAttributes(true);
150+
$dns = [];
151+
$filterParts = [];
152+
$bytes = 0;
153+
foreach ($members as $mid) {
154+
if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
155+
$parts = explode('@', $mid); //making sure we get only the uid
156+
$mid = $parts[0];
157+
}
158+
$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
159+
$filterParts[] = $filter;
160+
$bytes += strlen($filter);
161+
if ($bytes >= 9000000) {
162+
// AD has a default input buffer of 10 MB, we do not want
163+
// to take even the chance to exceed it
164+
$filter = $this->access->combineFilterWithOr($filterParts);
165+
$users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
166+
$bytes = 0;
167+
$filterParts = [];
168+
$dns = array_merge($dns, $users);
169+
}
170+
}
171+
if (count($filterParts) > 0) {
151172
$filter = $this->access->combineFilterWithOr($filterParts);
152-
$bytes = 0;
153-
$filterParts = [];
154173
$users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
155174
$dns = array_merge($dns, $users);
156175
}
157-
}
158-
if (count($filterParts) > 0) {
159-
$filter = $this->access->combineFilterWithOr($filterParts);
160-
$users = $this->access->fetchListOfUsers($filter, $requestAttributes, count($filterParts));
161-
$dns = array_merge($dns, $users);
162-
}
163-
$members = $dns;
176+
$members = $dns;
177+
break;
164178
}
165179

166180
$isInGroup = in_array($userDN, $members);
@@ -673,8 +687,8 @@ public function getUserGroups($uid) {
673687
// memberof doesn't support memberuid, so skip it here.
674688
if ((int)$this->access->connection->hasMemberOfFilterSupport === 1
675689
&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
676-
&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
677-
) {
690+
&& $this->ldapGroupMemberAssocAttr !== 'memberuid'
691+
&& $this->ldapGroupMemberAssocAttr !== 'zimbramailforwardingaddress') {
678692
$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
679693
if (is_array($groupDNs)) {
680694
foreach ($groupDNs as $dn) {
@@ -698,27 +712,33 @@ public function getUserGroups($uid) {
698712
}
699713

700714
//uniqueMember takes DN, memberuid the uid, so we need to distinguish
701-
if ((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
702-
|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
703-
) {
704-
$uid = $userDN;
705-
} elseif (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
706-
$result = $this->access->readAttribute($userDN, 'uid');
707-
if ($result === false) {
708-
$this->logger->debug('No uid attribute found for DN {dn} on {host}',
709-
[
710-
'app' => 'user_ldap',
711-
'dn' => $userDN,
712-
'host' => $this->access->connection->ldapHost,
713-
]
714-
);
715-
$uid = false;
716-
} else {
717-
$uid = $result[0];
718-
}
719-
} else {
720-
// just in case
721-
$uid = $userDN;
715+
switch ($this->ldapGroupMemberAssocAttr) {
716+
case 'uniquemember':
717+
case 'member':
718+
$uid = $userDN;
719+
break;
720+
721+
case 'memberuid':
722+
case 'zimbramailforwardingaddress':
723+
$result = $this->access->readAttribute($userDN, 'uid');
724+
if ($result === false) {
725+
$this->logger->debug('No uid attribute found for DN {dn} on {host}',
726+
[
727+
'app' => 'user_ldap',
728+
'dn' => $userDN,
729+
'host' => $this->access->connection->ldapHost,
730+
]
731+
);
732+
$uid = false;
733+
} else {
734+
$uid = $result[0];
735+
}
736+
break;
737+
738+
default:
739+
// just in case
740+
$uid = $userDN;
741+
break;
722742
}
723743

724744
if ($uid !== false) {
@@ -759,6 +779,12 @@ private function getGroupsByMember(string $dn, array &$seen = null): array {
759779
$allGroups = [];
760780
$seen[$dn] = true;
761781
$filter = $this->access->connection->ldapGroupMemberAssocAttr . '=' . $dn;
782+
783+
if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
784+
//in this case the member entries are email addresses
785+
$filter .= '@*';
786+
}
787+
762788
$groups = $this->access->fetchListOfGroups($filter,
763789
[strtolower($this->access->connection->ldapGroupMemberAssocAttr), $this->access->connection->ldapGroupDisplayName, 'dn']);
764790
if (is_array($groups)) {
@@ -768,6 +794,11 @@ private function getGroupsByMember(string $dn, array &$seen = null): array {
768794
}
769795
return $this->getGroupsByMember($dn, $seen);
770796
};
797+
798+
if (empty($dn)) {
799+
$dn = "";
800+
}
801+
771802
$allGroups = $this->walkNestedGroups($dn, $fetcher, $groups);
772803
}
773804
$visibleGroups = $this->filterValidGroups($allGroups);
@@ -828,50 +859,57 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
828859
}
829860

830861
$groupUsers = [];
831-
$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
832862
$attrs = $this->access->userManager->getAttributes(true);
833863
foreach ($members as $member) {
834-
if ($isMemberUid) {
835-
//we got uids, need to get their DNs to 'translate' them to user names
836-
$filter = $this->access->combineFilterWithAnd([
837-
str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
838-
$this->access->combineFilterWithAnd([
839-
$this->access->getFilterPartForUserSearch($search),
840-
$this->access->connection->ldapUserFilter
841-
])
842-
]);
843-
$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
844-
if (count($ldap_users) < 1) {
845-
continue;
846-
}
847-
$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
848-
} else {
849-
//we got DNs, check if we need to filter by search or we can give back all of them
850-
$uid = $this->access->dn2username($member);
851-
if (!$uid) {
852-
continue;
853-
}
854-
855-
$cacheKey = 'userExistsOnLDAP' . $uid;
856-
$userExists = $this->access->connection->getFromCache($cacheKey);
857-
if ($userExists === false) {
858-
continue;
859-
}
860-
if ($userExists === null || $search !== '') {
861-
if (!$this->access->readAttribute($member,
862-
$this->access->connection->ldapUserDisplayName,
864+
switch ($this->ldapGroupMemberAssocAttr) {
865+
case 'zimbramailforwardingaddress':
866+
//we get email addresses and need to convert them to uids
867+
$parts = explode('@', $member);
868+
$member = $parts[0];
869+
//no break needed because we just needed to remove the email part and now we have uids
870+
case 'memberuid':
871+
//we got uids, need to get their DNs to 'translate' them to user names
872+
$filter = $this->access->combineFilterWithAnd([
873+
str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
863874
$this->access->combineFilterWithAnd([
864875
$this->access->getFilterPartForUserSearch($search),
865876
$this->access->connection->ldapUserFilter
866-
]))) {
867-
if ($search === '') {
868-
$this->access->connection->writeToCache($cacheKey, false);
869-
}
877+
])
878+
]);
879+
$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
880+
if (count($ldap_users) < 1) {
870881
continue;
871882
}
872-
$this->access->connection->writeToCache($cacheKey, true);
873-
}
874-
$groupUsers[] = $uid;
883+
$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
884+
break;
885+
default:
886+
//we got DNs, check if we need to filter by search or we can give back all of them
887+
$uid = $this->access->dn2username($member);
888+
if (!$uid) {
889+
continue;
890+
}
891+
892+
$cacheKey = 'userExistsOnLDAP' . $uid;
893+
$userExists = $this->access->connection->getFromCache($cacheKey);
894+
if ($userExists === false) {
895+
continue;
896+
}
897+
if ($userExists === null || $search !== '') {
898+
if (!$this->access->readAttribute($member,
899+
$this->access->connection->ldapUserDisplayName,
900+
$this->access->combineFilterWithAnd([
901+
$this->access->getFilterPartForUserSearch($search),
902+
$this->access->connection->ldapUserFilter
903+
]))) {
904+
if ($search === '') {
905+
$this->access->connection->writeToCache($cacheKey, false);
906+
}
907+
continue;
908+
}
909+
$this->access->connection->writeToCache($cacheKey, true);
910+
}
911+
$groupUsers[] = $uid;
912+
break;
875913
}
876914
}
877915

@@ -930,8 +968,8 @@ public function countUsersInGroup($gid, $search = '') {
930968
}
931969
$search = $this->access->escapeFilterPart($search, true);
932970
$isMemberUid =
933-
(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
934-
=== 'memberuid');
971+
($this->ldapGroupMemberAssocAttr === 'memberuid' ||
972+
$this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress');
935973

936974
//we need to apply the search filter
937975
//alternatives that need to be checked:
@@ -944,6 +982,11 @@ public function countUsersInGroup($gid, $search = '') {
944982
$groupUsers = [];
945983
foreach ($members as $member) {
946984
if ($isMemberUid) {
985+
if ($this->ldapGroupMemberAssocAttr === 'zimbramailforwardingaddress') {
986+
//we get email addresses and need to convert them to uids
987+
$parts = explode('@', $member);
988+
$member = $parts[0];
989+
}
947990
//we got uids, need to get their DNs to 'translate' them to user names
948991
$filter = $this->access->combineFilterWithAnd([
949992
str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),

apps/user_ldap/lib/Wizard.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ private function checkHost() {
794794
* @throws \Exception
795795
*/
796796
private function detectGroupMemberAssoc() {
797-
$possibleAttrs = ['uniqueMember', 'memberUid', 'member', 'gidNumber'];
797+
$possibleAttrs = ['uniqueMember', 'memberUid', 'member', 'gidNumber', 'zimbraMailForwardingAddress'];
798798
$filter = $this->configuration->ldapGroupFilter;
799799
if (empty($filter)) {
800800
return false;

apps/user_ldap/templates/settings.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,10 @@
103103
p(' selected');
104104
} ?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) {
105105
p(' selected');
106-
} ?>>gidNumber</option></select></p> <p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p>
106+
} ?>>gidNumber</option><option value="zimbraMailForwardingAddress"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'zimbraMailForwardingAddress')) {
107+
p(' selected');
108+
} ?>>zimbraMailForwardingAddress</option></select></p>
109+
<p><label for="ldap_dynamic_group_member_url"><?php p($l->t('Dynamic Group Member URL'));?></label><input type="text" id="ldap_dynamic_group_member_url" name="ldap_dynamic_group_member_url" title="<?php p($l->t('The LDAP attribute that on group objects contains an LDAP search URL that determines what objects belong to the group. (An empty setting disables dynamic group membership functionality.)'));?>" data-default="<?php p($_['ldap_dynamic_group_member_url_default']); ?>" /></p>
107110
<p><label for="ldap_nested_groups"><?php p($l->t('Nested Groups'));?></label><input type="checkbox" id="ldap_nested_groups" name="ldap_nested_groups" value="1" data-default="<?php p($_['ldap_nested_groups_default']); ?>" title="<?php p($l->t('When switched on, groups that contain groups are supported. (Only works if the group member attribute contains DNs.)'));?>" /></p>
108111
<p><label for="ldap_paging_size"><?php p($l->t('Paging chunksize'));?></label><input type="number" id="ldap_paging_size" name="ldap_paging_size" title="<?php p($l->t('Chunksize used for paged LDAP searches that may return bulky results like user or group enumeration. (Setting it 0 disables paged LDAP searches in those situations.)'));?>" data-default="<?php p($_['ldap_paging_size_default']); ?>" /></p>
109112
<p><label for="ldap_turn_on_pwd_change"><?php p($l->t('Enable LDAP password changes per user'));?></label><span class="inlinetable"><span class="tablerow left"><input type="checkbox" id="ldap_turn_on_pwd_change" name="ldap_turn_on_pwd_change" value="1" data-default="<?php p($_['ldap_turn_on_pwd_change_default']); ?>" title="<?php p($l->t('Allow LDAP users to change their password and allow Super Administrators and Group Administrators to change the password of their LDAP users. Only works when access control policies are configured accordingly on the LDAP server. As passwords are sent in plaintext to the LDAP server, transport encryption must be used and password hashing should be configured on the LDAP server.'));?>" /><span class="tablecell"><?php p($l->t('(New password is sent as plain text to LDAP)'));?></span></span>

0 commit comments

Comments
 (0)