Skip to content

Commit

Permalink
added "zimbraMailForwardingAddress" as a Group-Member association att…
Browse files Browse the repository at this point in the history
…ribute to enable the use of Zimbra Distribution Lists as groups in nextcloud when connecting to a zimbra LDAP

Signed-off-by: Tobias Perschon <tp@bitconnect.at>
  • Loading branch information
tofuSCHNITZEL committed Mar 20, 2019
1 parent 6458dd8 commit cb91152
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 70 deletions.
177 changes: 109 additions & 68 deletions apps/user_ldap/lib/Group_LDAP.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,37 +129,52 @@ public function inGroup($uid, $gid) {

//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
$members = $this->_groupMembers($groupDN);
$members = array_keys($members); // uids are returned as keys

if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'zimbramailforwardingaddress'
&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid' ){

$members = array_keys($members);
// DNs are returned as keys; todo: this is probably only the case if
// nested groups are used and group member attributes are DNs - needs fixing
}

if(!is_array($members) || count($members) === 0) {
$this->access->connection->writeToCache($cacheKey, false);
return false;
}

//extra work if we don't get back user DNs
if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
$dns = array();
$filterParts = array();
$bytes = 0;
foreach($members as $mid) {
$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
$filterParts[] = $filter;
$bytes += strlen($filter);
if($bytes >= 9000000) {
// AD has a default input buffer of 10 MB, we do not want
// to take even the chance to exceed it
$filter = $this->access->combineFilterWithOr($filterParts);
$bytes = 0;
$filterParts = array();
$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
$dns = array_merge($dns, $users);
}
}
if(count($filterParts) > 0) {
$filter = $this->access->combineFilterWithOr($filterParts);
$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
$dns = array_merge($dns, $users);
}
$members = $dns;
switch(strtolower($this->access->connection->ldapGroupMemberAssocAttr)){
case "memberuid":
case "zimbramailforwardingaddress":
$dns = array();
$filterParts = array();
$bytes = 0;
foreach($members as $mid) {
if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'zimbramailforwardingaddress'){
$parts = explode("@", $mid); //making sure we get only the uid
$mid = $parts[0];
}
$filter = str_replace('%uid', $mid, $this->access->connection->ldapLoginFilter);
$filterParts[] = $filter;
$bytes += strlen($filter);
if($bytes >= 9000000) {
// AD has a default input buffer of 10 MB, we do not want
// to take even the chance to exceed it
$filter = $this->access->combineFilterWithOr($filterParts);
$bytes = 0;
$filterParts = array();
$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
$dns = array_merge($dns, $users);
}
}
if(count($filterParts) > 0) {
$filter = $this->access->combineFilterWithOr($filterParts);
$users = $this->access->fetchListOfUsers($filter, 'dn', count($filterParts));
$dns = array_merge($dns, $users);
}
$members = $dns;
break;
}

$isInGroup = in_array($userDN, $members);
Expand Down Expand Up @@ -706,6 +721,7 @@ public function getUserGroups($uid) {
if((int)$this->access->connection->hasMemberOfFilterSupport === 1
&& (int)$this->access->connection->useMemberOfToDetectMembership === 1
&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'memberuid'
&& strtolower($this->access->connection->ldapGroupMemberAssocAttr) !== 'zimbramailforwardingaddress'
) {
$groupDNs = $this->_getGroupDNsFromMemberOf($userDN);
if (is_array($groupDNs)) {
Expand All @@ -730,21 +746,27 @@ public function getUserGroups($uid) {
}

//uniqueMember takes DN, memberuid the uid, so we need to distinguish
if((strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'uniquemember')
|| (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'member')
) {
$uid = $userDN;
} else if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid') {
$result = $this->access->readAttribute($userDN, 'uid');
if ($result === false) {
\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
$this->access->connection->ldapHost, ILogger::DEBUG);
}
$uid = $result[0];
} else {
// just in case
$uid = $userDN;
}
switch (strtolower($this->access->connection->ldapGroupMemberAssocAttr)){
case "uniquemember":
case "member":
$uid = $userDN;
break;

case "memberuid":
case "zimbramailforwardingaddress":
$result = $this->access->readAttribute($userDN, 'uid');
if ($result === false) {
\OCP\Util::writeLog('user_ldap', 'No uid attribute found for DN ' . $userDN . ' on '.
$this->access->connection->ldapHost, ILogger::DEBUG);
}
$uid = $result[0];
break;

default:
// just in case
$uid = $userDN;
break;
}

if(isset($this->cachedGroupsByMember[$uid])) {
$groups = array_merge($groups, $this->cachedGroupsByMember[$uid]);
Expand Down Expand Up @@ -784,6 +806,11 @@ private function getGroupsByMember($dn, &$seen = null) {
$allGroups = [];
$seen[$dn] = true;
$filter = $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn;

if (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === "zimbramailforwardingaddress")
//in this case the member entries are email addresses
$filter .= '@*';

$groups = $this->access->fetchListOfGroups($filter,
[$this->access->connection->ldapGroupDisplayName, 'dn']);
if (is_array($groups)) {
Expand Down Expand Up @@ -851,34 +878,43 @@ public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
}

$groupUsers = array();
$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
$attrs = $this->access->userManager->getAttributes(true);
foreach($members as $member) {
if($isMemberUid) {
//we got uids, need to get their DNs to 'translate' them to user names
$filter = $this->access->combineFilterWithAnd(array(
str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
$this->access->getFilterPartForUserSearch($search)
));
$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
if(count($ldap_users) < 1) {
continue;
}
$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
} else {
//we got DNs, check if we need to filter by search or we can give back all of them
if ($search !== '') {
if(!$this->access->readAttribute($member,
$this->access->connection->ldapUserDisplayName,
$this->access->getFilterPartForUserSearch($search))) {
continue;
}
}
// dn2username will also check if the users belong to the allowed base
if($ocname = $this->access->dn2username($member)) {
$groupUsers[] = $ocname;
}
}
switch (strtolower($this->access->connection->ldapGroupMemberAssocAttr)){
case "zimbramailforwardingaddress":
//we get email addresses and need to convert them to uids
$parts = explode("@", $member);
$member = $parts[0];
//no break needed because we just needed to remove the email part and now we have uids
case "memberuid":
//we got uids, need to get their DNs to 'translate' them to user names
$filter = $this->access->combineFilterWithAnd(array(
str_replace('%uid', trim($member), $this->access->connection->ldapLoginFilter),
$this->access->getFilterPartForUserSearch($search)
));
$ldap_users = $this->access->fetchListOfUsers($filter, $attrs, 1);
if(count($ldap_users) < 1) {
continue;
}
$groupUsers[] = $this->access->dn2username($ldap_users[0]['dn'][0]);
break;


default:
//we got DNs, check if we need to filter by search or we can give back all of them
if ($search !== '') {
if(!$this->access->readAttribute($member,
$this->access->connection->ldapUserDisplayName,
$this->access->getFilterPartForUserSearch($search))) {
continue;
}
}
// dn2username will also check if the users belong to the allowed base
if($ocname = $this->access->dn2username($member)) {
$groupUsers[] = $ocname;
}
break;
}
}

$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers, $posixGroupUsers));
Expand Down Expand Up @@ -933,8 +969,8 @@ public function countUsersInGroup($gid, $search = '') {
}
$search = $this->access->escapeFilterPart($search, true);
$isMemberUid =
(strtolower($this->access->connection->ldapGroupMemberAssocAttr)
=== 'memberuid');
(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid' ||
strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'zimbramailforwardingaddress');

//we need to apply the search filter
//alternatives that need to be checked:
Expand All @@ -947,6 +983,11 @@ public function countUsersInGroup($gid, $search = '') {
$groupUsers = array();
foreach($members as $member) {
if($isMemberUid) {
if(strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'zimbramailforwardingaddress'){
//we get email addresses and need to convert them to uids
$parts = explode("@", $member);
$member = $parts[0];
}
//we got uids, need to get their DNs to 'translate' them to user names
$filter = $this->access->combineFilterWithAnd(array(
str_replace('%uid', $member, $this->access->connection->ldapLoginFilter),
Expand Down
2 changes: 1 addition & 1 deletion apps/user_ldap/lib/Wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ private function checkHost() {
* @throws \Exception
*/
private function detectGroupMemberAssoc() {
$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber');
$possibleAttrs = array('uniqueMember', 'memberUid', 'member', 'gidNumber', 'zimbraMailForwardingAddress');
$filter = $this->configuration->ldapGroupFilter;
if(empty($filter)) {
return false;
Expand Down
3 changes: 2 additions & 1 deletion apps/user_ldap/templates/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@
<p><label for="ldap_group_display_name"><?php p($l->t('Group Display Name Field'));?></label><input type="text" id="ldap_group_display_name" name="ldap_group_display_name" data-default="<?php p($_['ldap_group_display_name_default']); ?>" title="<?php p($l->t('The LDAP attribute to use to generate the groups\'s display name.'));?>" /></p>
<p><label for="ldap_base_groups"><?php p($l->t('Base Group Tree'));?></label><textarea id="ldap_base_groups" name="ldap_base_groups" placeholder="<?php p($l->t('One Group Base DN per line'));?>" data-default="<?php p($_['ldap_base_groups_default']); ?>" title="<?php p($l->t('Base Group Tree'));?>"></textarea></p>
<p><label for="ldap_attributes_for_group_search"><?php p($l->t('Group Search Attributes'));?></label><textarea id="ldap_attributes_for_group_search" name="ldap_attributes_for_group_search" placeholder="<?php p($l->t('Optional; one attribute per line'));?>" data-default="<?php p($_['ldap_attributes_for_group_search_default']); ?>" title="<?php p($l->t('Group Search Attributes'));?>"></textarea></p>
<p><label for="ldap_group_member_assoc_attribute"><?php p($l->t('Group-Member association'));?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute" data-default="<?php p($_['ldap_group_member_assoc_attribute_default']); ?>" ><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'uniqueMember')) p(' selected'); ?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'memberUid')) p(' selected'); ?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'member')) p(' selected'); ?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) p(' selected'); ?>>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>
<p><label for="ldap_group_member_assoc_attribute"><?php p($l->t('Group-Member association'));?></label><select id="ldap_group_member_assoc_attribute" name="ldap_group_member_assoc_attribute" data-default="<?php p($_['ldap_group_member_assoc_attribute_default']); ?>" ><option value="uniqueMember"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'uniqueMember')) p(' selected'); ?>>uniqueMember</option><option value="memberUid"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'memberUid')) p(' selected'); ?>>memberUid</option><option value="member"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'member')) p(' selected'); ?>>member (AD)</option><option value="gidNumber"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'gidNumber')) p(' selected'); ?>>gidNumber</option><option value="zimbraMailForwardingAddress"<?php if (isset($_['ldap_group_member_assoc_attribute']) && ($_['ldap_group_member_assoc_attribute'] === 'zimbraMailForwardingAddress')) p(' selected'); ?>>zimbraMailForwardingAddress</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>
<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>
<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>
<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>
Expand Down

0 comments on commit cb91152

Please sign in to comment.