Skip to content
Merged
2 changes: 1 addition & 1 deletion apps/user_ldap/ajax/wizard.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

$con = new \OCA\User_LDAP\Connection($ldapWrapper, $prefix, null);
$con->setConfiguration($configuration->getConfiguration());
$con->ldapConfigurationActive = true;
$con->ldapConfigurationActive = (string)true;
$con->setIgnoreValidation(true);

$factory = \OC::$server->get(\OCA\User_LDAP\AccessFactory::class);
Expand Down
117 changes: 96 additions & 21 deletions apps/user_ldap/lib/Access.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,56 @@ public function getConnection() {
return $this->connection;
}

/**
* Reads several attributes for an LDAP record identified by a DN and a filter
* No support for ranged attributes.
*
* @param string $dn the record in question
* @param array $attrs the attributes that shall be retrieved
* if empty, just check the record's existence
* @param string $filter
* @return array|false an array of values on success or an empty
* array if $attr is empty, false otherwise
* @throws ServerNotAvailableException
*/
public function readAttributes(string $dn, array $attrs, string $filter = 'objectClass=*'): array|false {
if (!$this->checkConnection()) {
$this->logger->warning(
'No LDAP Connector assigned, access impossible for readAttribute.',
['app' => 'user_ldap']
);
return false;
}
$cr = $this->connection->getConnectionResource();
$attrs = array_map(
fn (string $attr): string => mb_strtolower($attr, 'UTF-8'),
$attrs,
);

$values = [];
$record = $this->executeRead($dn, $attrs, $filter);
if (is_bool($record)) {
// when an exists request was run and it was successful, an empty
// array must be returned
return $record ? [] : false;
}

$result = [];
foreach ($attrs as $attr) {
$values = $this->extractAttributeValuesFromResult($record, $attr);
if (!empty($values)) {
$result[$attr] = $values;
}
}

if (!empty($result)) {
return $result;
}

$this->logger->debug('Requested attributes {attrs} not found for ' . $dn, ['app' => 'user_ldap', 'attrs' => $attrs]);
return false;
}

/**
* reads a given attribute for an LDAP record identified by a DN
*
Expand Down Expand Up @@ -248,9 +298,9 @@ public function readAttribute(string $dn, string $attr, string $filter = 'object
* returned data on a successful usual operation
* @throws ServerNotAvailableException
*/
public function executeRead(string $dn, string $attribute, string $filter) {
public function executeRead(string $dn, string|array $attribute, string $filter) {
$dn = $this->helper->DNasBaseParameter($dn);
$rr = @$this->invokeLDAPMethod('read', $dn, $filter, [$attribute]);
$rr = @$this->invokeLDAPMethod('read', $dn, $filter, (is_string($attribute) ? [$attribute] : $attribute));
if (!$this->ldap->isResource($rr)) {
if ($attribute !== '') {
//do not throw this message on userExists check, irritates
Expand Down Expand Up @@ -472,15 +522,15 @@ public function dn2groupname($fdn, $ldapName = null, bool $autoMapping = true) {
* @return string|false with with the name to use in Nextcloud
* @throws \Exception
*/
public function dn2username($fdn, $ldapName = null) {
public function dn2username($fdn) {
//To avoid bypassing the base DN settings under certain circumstances
//with the group support, check whether the provided DN matches one of
//the given Bases
if (!$this->isDNPartOfBase($fdn, $this->connection->ldapBaseUsers)) {
return false;
}

return $this->dn2ocname($fdn, $ldapName, true);
return $this->dn2ocname($fdn, null, true);
}

/**
Expand All @@ -504,12 +554,8 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
$newlyMapped = false;
if ($isUser) {
$mapper = $this->getUserMapper();
$nameAttribute = $this->connection->ldapUserDisplayName;
$filter = $this->connection->ldapUserFilter;
} else {
$mapper = $this->getGroupMapper();
$nameAttribute = $this->connection->ldapGroupDisplayName;
$filter = $this->connection->ldapGroupFilter;
}

//let's try to retrieve the Nextcloud name from the mappings table
Expand All @@ -523,6 +569,36 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
return false;
}

if ($isUser) {
$nameAttribute = strtolower($this->connection->ldapUserDisplayName);
$filter = $this->connection->ldapUserFilter;
$uuidAttr = 'ldapUuidUserAttribute';
$uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
$usernameAttribute = strtolower($this->connection->ldapExpertUsernameAttr);
$attributesToRead = [$nameAttribute,$usernameAttribute];
// TODO fetch also display name attributes and cache them if the user is mapped
} else {
$nameAttribute = strtolower($this->connection->ldapGroupDisplayName);
$filter = $this->connection->ldapGroupFilter;
$uuidAttr = 'ldapUuidGroupAttribute';
$uuidOverride = $this->connection->ldapExpertUUIDGroupAttr;
$attributesToRead = [$nameAttribute];
}

if ($this->detectUuidAttribute($fdn, $isUser, false, $record)) {
$attributesToRead[] = $this->connection->$uuidAttr;
}

if ($record === null) {
/* No record was passed, fetch it */
$record = $this->readAttributes($fdn, $attributesToRead, $filter);
if ($record === false) {
$this->logger->debug('Cannot read attributes for ' . $fdn . '. Skipping.', ['filter' => $filter]);
$intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
return false;
}
}

//second try: get the UUID and check if it is known. Then, update the DN and return the name.
$uuid = $this->getUUID($fdn, $isUser, $record);
if (is_string($uuid)) {
Expand All @@ -537,20 +613,9 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
return false;
}

if (is_null($ldapName)) {
$ldapName = $this->readAttribute($fdn, $nameAttribute, $filter);
if (!isset($ldapName[0]) || empty($ldapName[0])) {
$this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']);
$intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
return false;
}
$ldapName = $ldapName[0];
}

if ($isUser) {
$usernameAttribute = (string)$this->connection->ldapExpertUsernameAttr;
if ($usernameAttribute !== '') {
$username = $this->readAttribute($fdn, $usernameAttribute);
$username = $record[$usernameAttribute];
if (!isset($username[0]) || empty($username[0])) {
$this->logger->debug('No or empty username (' . $usernameAttribute . ') for ' . $fdn . '.', ['app' => 'user_ldap']);
return false;
Expand All @@ -572,6 +637,15 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
return false;
}
} else {
if (is_null($ldapName)) {
$ldapName = $record[$nameAttribute];
if (!isset($ldapName[0]) || empty($ldapName[0])) {
$this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']);
$intermediates['group-' . $fdn] = true;
return false;
}
$ldapName = $ldapName[0];
}
$intName = $this->sanitizeGroupIDCandidate($ldapName);
}

Expand All @@ -589,6 +663,7 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
$this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]);
$newlyMapped = $this->mapAndAnnounceIfApplicable($mapper, $fdn, $intName, $uuid, $isUser);
if ($newlyMapped) {
$this->logger->debug('Mapped {fdn} as {name}', ['fdn' => $fdn,'name' => $intName]);
return $intName;
}
}
Expand All @@ -603,7 +678,6 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
'fdn' => $fdn,
'altName' => $altName,
'intName' => $intName,
'app' => 'user_ldap',
]
);
$newlyMapped = true;
Expand Down Expand Up @@ -728,6 +802,7 @@ public function cacheUserHome(string $ocName, $home): void {
*/
public function cacheUserExists(string $ocName): void {
$this->connection->writeToCache('userExists' . $ocName, true);
$this->connection->writeToCache('userExistsOnLDAP' . $ocName, true);
}

/**
Expand Down
93 changes: 91 additions & 2 deletions apps/user_ldap/lib/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,76 @@
use Psr\Log\LoggerInterface;

/**
* @property int ldapPagingSize holds an integer
* @property string ldapUserAvatarRule
* @property string $ldapHost
* @property string $ldapPort
* @property string $ldapBackupHost
* @property string $ldapBackupPort
* @property string $ldapBackgroundHost
* @property string $ldapBackgroundPort
* @property array|'' $ldapBase
* @property array|'' $ldapBaseUsers
* @property array|'' $ldapBaseGroups
* @property string $ldapAgentName
* @property string $ldapAgentPassword
* @property string $ldapTLS
* @property string $turnOffCertCheck
* @property string $ldapIgnoreNamingRules
* @property string $ldapUserDisplayName
* @property string $ldapUserDisplayName2
* @property string $ldapUserAvatarRule
* @property string $ldapGidNumber
* @property array|'' $ldapUserFilterObjectclass
* @property array|'' $ldapUserFilterGroups
* @property string $ldapUserFilter
* @property string $ldapUserFilterMode
* @property string $ldapGroupFilter
* @property string $ldapGroupFilterMode
* @property array|'' $ldapGroupFilterObjectclass
* @property array|'' $ldapGroupFilterGroups
* @property string $ldapGroupDisplayName
* @property string $ldapGroupMemberAssocAttr
* @property string $ldapLoginFilter
* @property string $ldapLoginFilterMode
* @property string $ldapLoginFilterEmail
* @property string $ldapLoginFilterUsername
* @property array|'' $ldapLoginFilterAttributes
* @property string $ldapQuotaAttribute
* @property string $ldapQuotaDefault
* @property string $ldapEmailAttribute
* @property string $ldapCacheTTL
* @property string $ldapUuidUserAttribute
* @property string $ldapUuidGroupAttribute
* @property string $ldapOverrideMainServer
* @property string $ldapConfigurationActive
* @property array|'' $ldapAttributesForUserSearch
* @property array|'' $ldapAttributesForGroupSearch
* @property string $ldapExperiencedAdmin
* @property string $homeFolderNamingRule
* @property string $hasMemberOfFilterSupport
* @property string $useMemberOfToDetectMembership
* @property string $ldapExpertUsernameAttr
* @property string $ldapExpertUUIDUserAttr
* @property string $ldapExpertUUIDGroupAttr
* @property string $markRemnantsAsDisabled
* @property string $lastJpegPhotoLookup
* @property string $ldapNestedGroups
* @property string $ldapPagingSize
* @property string $turnOnPasswordChange
* @property string $ldapDynamicGroupMemberURL
* @property string $ldapDefaultPPolicyDN
* @property string $ldapExtStorageHomeAttribute
* @property string $ldapMatchingRuleInChainState
* @property string $ldapConnectionTimeout
* @property string $ldapAttributePhone
* @property string $ldapAttributeWebsite
* @property string $ldapAttributeAddress
* @property string $ldapAttributeTwitter
* @property string $ldapAttributeFediverse
* @property string $ldapAttributeOrganisation
* @property string $ldapAttributeRole
* @property string $ldapAttributeHeadline
* @property string $ldapAttributeBiography
* @property string $ldapAdminGroup
*/
class Configuration {
public const AVATAR_PREFIX_DEFAULT = 'default';
Expand Down Expand Up @@ -252,6 +320,27 @@ public function readConfiguration(): void {
break;
case 'ldapUserDisplayName2':
case 'ldapGroupDisplayName':
case 'ldapGidNumber':
case 'ldapGroupMemberAssocAttr':
case 'ldapQuotaAttribute':
case 'ldapEmailAttribute':
case 'ldapUuidUserAttribute':
case 'ldapUuidGroupAttribute':
case 'ldapExpertUsernameAttr':
case 'ldapExpertUUIDUserAttr':
case 'ldapExpertUUIDGroupAttr':
case 'ldapExtStorageHomeAttribute':
case 'ldapAttributePhone':
case 'ldapAttributeWebsite':
case 'ldapAttributeAddress':
case 'ldapAttributeTwitter':
case 'ldapAttributeFediverse':
case 'ldapAttributeOrganisation':
case 'ldapAttributeRole':
case 'ldapAttributeHeadline':
case 'ldapAttributeBiography':
case 'ldapAttributeBirthDate':
case 'ldapAttributeAnniversaryDate':
$readMethod = 'getLcValue';
break;
case 'ldapUserDisplayName':
Expand Down
Loading
Loading