diff --git a/apps/user_ldap/ajax/wizard.php b/apps/user_ldap/ajax/wizard.php index e8a9ce0b0012f..fac0a443247e6 100644 --- a/apps/user_ldap/ajax/wizard.php +++ b/apps/user_ldap/ajax/wizard.php @@ -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); diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index 2feeaa9b7fe72..706daac515d48 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -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 * @@ -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 @@ -472,7 +522,7 @@ 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 @@ -480,7 +530,7 @@ public function dn2username($fdn, $ldapName = null) { return false; } - return $this->dn2ocname($fdn, $ldapName, true); + return $this->dn2ocname($fdn, null, true); } /** @@ -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 @@ -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)) { @@ -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; @@ -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); } @@ -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; } } @@ -603,7 +678,6 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped 'fdn' => $fdn, 'altName' => $altName, 'intName' => $intName, - 'app' => 'user_ldap', ] ); $newlyMapped = true; @@ -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); } /** diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php index b0bb600cb3bec..cf44bcc009406 100644 --- a/apps/user_ldap/lib/Configuration.php +++ b/apps/user_ldap/lib/Configuration.php @@ -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'; @@ -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': diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index 69e028830fa52..dba6e69f7c4e3 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -41,49 +41,79 @@ use Psr\Log\LoggerInterface; /** - * magic properties (incomplete) + * magic properties * responsible for LDAP connections in context with the provided configuration * - * @property string ldapHost - * @property string ldapPort holds the port number - * @property string ldapUserFilter - * @property string ldapUserDisplayName - * @property string ldapUserDisplayName2 - * @property string ldapUserAvatarRule - * @property boolean turnOnPasswordChange - * @property string[] ldapBaseUsers - * @property int|null ldapPagingSize holds an integer - * @property bool|mixed|void ldapGroupMemberAssocAttr - * @property string ldapUuidUserAttribute - * @property string ldapUuidGroupAttribute - * @property string ldapExpertUUIDUserAttr - * @property string ldapExpertUUIDGroupAttr - * @property string ldapQuotaAttribute - * @property string ldapQuotaDefault - * @property string ldapEmailAttribute - * @property string ldapExtStorageHomeAttribute - * @property string homeFolderNamingRule - * @property bool|string markRemnantsAsDisabled - * @property bool|string ldapNestedGroups - * @property string[] ldapBaseGroups - * @property string ldapGroupFilter - * @property string ldapGroupDisplayName - * @property string ldapLoginFilter - * @property string ldapDynamicGroupMemberURL - * @property string ldapGidNumber - * @property int hasMemberOfFilterSupport - * @property int useMemberOfToDetectMembership - * @property string ldapMatchingRuleInChainState - * @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 + * @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 Connection extends LDAPUtility { /** @@ -453,7 +483,7 @@ private function doSoftValidation() { if ((stripos((string)$this->configuration->ldapHost, 'ldaps://') === 0) && $this->configuration->ldapTLS) { - $this->configuration->ldapTLS = false; + $this->configuration->ldapTLS = (string)false; $this->logger->info( 'LDAPS (already using secure connection) and TLS do not work together. Switched off TLS.', ['app' => 'user_ldap'] diff --git a/apps/user_ldap/lib/Jobs/Sync.php b/apps/user_ldap/lib/Jobs/Sync.php index b92aa2fcfaa90..16371df156285 100644 --- a/apps/user_ldap/lib/Jobs/Sync.php +++ b/apps/user_ldap/lib/Jobs/Sync.php @@ -169,7 +169,7 @@ public function runCycle($cycleData) { $results = $access->fetchListOfUsers( $filter, $access->userManager->getAttributes(), - $connection->ldapPagingSize, + (int)$connection->ldapPagingSize, $cycleData['offset'], true ); diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index 9d3ba333e89ef..43d8e97d6b8ee 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -128,6 +128,7 @@ public function invalidate($uid) { /** * @brief checks whether the Access instance has been set * @throws \Exception if Access has not been set + * @psalm-assert !null $this->access * @return null */ private function checkAccess() { @@ -258,4 +259,37 @@ public function get($id) { return $this->createInstancyByUserName($id); } + + /** + * @brief Checks whether a User object by its DN or Nextcloud username exists + * @param string $id the DN or username of the user + * @throws \Exception when connection could not be established + */ + public function exists($id): bool { + $this->checkAccess(); + $this->logger->debug('Checking if {id} exists', ['id' => $id]); + if (isset($this->usersByDN[$id])) { + return true; + } elseif (isset($this->usersByUid[$id])) { + return true; + } + + if ($this->access->stringResemblesDN($id)) { + $this->logger->debug('{id} looks like a dn', ['id' => $id]); + $uid = $this->access->dn2username($id); + if ($uid !== false) { + return true; + } + } + + // Most likely a uid. Check whether it is a deleted user + if ($this->isDeletedUser($id)) { + return true; + } + $dn = $this->access->username2dn($id); + if ($dn !== false) { + return true; + } + return false; + } } diff --git a/apps/user_ldap/lib/User_LDAP.php b/apps/user_ldap/lib/User_LDAP.php index b629f2f9452c2..3e01c634f9f21 100644 --- a/apps/user_ldap/lib/User_LDAP.php +++ b/apps/user_ldap/lib/User_LDAP.php @@ -355,10 +355,9 @@ public function userExists($uid) { if (!is_null($userExists)) { return (bool)$userExists; } - //getting dn, if false the user does not exist. If dn, he may be mapped only, requires more checking. - $user = $this->access->userManager->get($uid); + $userExists = $this->access->userManager->exists($uid); - if (is_null($user)) { + if (!$userExists) { $this->logger->debug( 'No DN found for '.$uid.' on '.$this->access->connection->ldapHost, ['app' => 'user_ldap'] @@ -644,7 +643,6 @@ public function createUser($username, $password) { $uuid, true ); - $this->access->cacheUserExists($username); } else { $this->logger->warning( 'Failed to map created LDAP user with userid {userid}, because UUID could not be determined', diff --git a/apps/user_ldap/lib/Wizard.php b/apps/user_ldap/lib/Wizard.php index 1f4794f164e6f..4799f202511b2 100644 --- a/apps/user_ldap/lib/Wizard.php +++ b/apps/user_ldap/lib/Wizard.php @@ -415,7 +415,7 @@ private function determineGroups(string $dbKey, string $confKey, bool $testMembe $this->fetchGroups($dbKey, $confKey); if ($testMemberOf) { - $this->configuration->hasMemberOfFilterSupport = $this->testMemberOf(); + $this->configuration->hasMemberOfFilterSupport = (string)$this->testMemberOf(); $this->result->markChange(); if (!$this->configuration->hasMemberOfFilterSupport) { throw new \Exception('memberOf is not supported by the server'); @@ -705,8 +705,8 @@ public function guessPortAndTLS() { if ($settingsFound === true) { $config = [ - 'ldapPort' => $p, - 'ldapTLS' => (int)$t + 'ldapPort' => (string)$p, + 'ldapTLS' => (string)$t, ]; $this->configuration->setConfiguration($config); $this->logger->debug( @@ -1329,7 +1329,7 @@ private function getConnection() { $this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3); $this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0); $this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT); - if ($this->configuration->ldapTLS === 1) { + if ($this->configuration->ldapTLS) { $this->ldap->startTls($cr); } @@ -1344,6 +1344,9 @@ private function getConnection() { return false; } + /** + * @return array + */ private function getDefaultLdapPortSettings(): array { static $settings = [ ['port' => 7636, 'tls' => false], @@ -1356,6 +1359,9 @@ private function getDefaultLdapPortSettings(): array { return $settings; } + /** + * @return array + */ private function getPortSettingsToTry(): array { //389 ← LDAP / Unencrypted or StartTLS //636 ← LDAPS / SSL @@ -1374,7 +1380,7 @@ private function getPortSettingsToTry(): array { } $portSettings[] = ['port' => $port, 'tls' => false]; } elseif ($this->configuration->usesLdapi()) { - $portSettings[] = ['port' => '', 'tls' => false]; + $portSettings[] = ['port' => 0, 'tls' => false]; } //default ports diff --git a/apps/user_ldap/tests/AccessTest.php b/apps/user_ldap/tests/AccessTest.php index 5469b9267e7a5..3aa3fe4b747f5 100644 --- a/apps/user_ldap/tests/AccessTest.php +++ b/apps/user_ldap/tests/AccessTest.php @@ -632,7 +632,8 @@ public function testFetchListOfUsers() { $this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries); - $this->connection->expects($this->exactly($fakeLdapEntries['count'])) + // Called twice per user, for userExists and userExistsOnLdap + $this->connection->expects($this->exactly(2 * $fakeLdapEntries['count'])) ->method('writeToCache') ->with($this->stringStartsWith('userExists'), true); diff --git a/apps/user_ldap/tests/User_LDAPTest.php b/apps/user_ldap/tests/User_LDAPTest.php index bc6b72e1778b7..84c1c4a661663 100644 --- a/apps/user_ldap/tests/User_LDAPTest.php +++ b/apps/user_ldap/tests/User_LDAPTest.php @@ -323,6 +323,10 @@ public function testDeleteUserSuccess() { $this->userManager->expects($this->atLeastOnce()) ->method('get') ->willReturn($offlineUser); + $this->userManager->expects($this->once()) + ->method('exists') + ->with($uid) + ->willReturn(true); $backend = new UserLDAP($this->access, $this->notificationManager, $this->pluginManager, $this->logger, $this->deletedUsersIndex); @@ -510,9 +514,12 @@ public function testUserExists() { $user = $this->createMock(User::class); - $this->userManager->expects($this->atLeastOnce()) - ->method('get') - ->willReturn($user); + $this->userManager->expects($this->never()) + ->method('get'); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('gunslinger') + ->willReturn(true); $this->access->expects($this->any()) ->method('getUserMapper') ->willReturn($this->createMock(UserMapping::class)); @@ -537,11 +544,12 @@ public function testUserExistsForDeleted() { ->method('getUserMapper') ->willReturn($mapper); - $user = $this->createMock(User::class); - - $this->userManager->expects($this->atLeastOnce()) - ->method('get') - ->willReturn($user); + $this->userManager->expects($this->never()) + ->method('get'); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('formerUser') + ->willReturn(true); //test for deleted user – always returns true as long as we have the user in DB $this->assertTrue($backend->userExists('formerUser')); @@ -584,9 +592,12 @@ public function testUserExistsPublicAPI() { } return false; }); - $this->userManager->expects($this->atLeastOnce()) - ->method('get') - ->willReturn($user); + $this->userManager->expects($this->never()) + ->method('get'); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('gunslinger') + ->willReturn(true); $this->access->expects($this->any()) ->method('getUserMapper') ->willReturn($this->createMock(UserMapping::class)); @@ -645,7 +656,12 @@ public function testGetHomeAbsolutePath() { $this->userManager->expects($this->atLeastOnce()) ->method('get') + ->with('gunslinger') ->willReturn($user); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('gunslinger') + ->willReturn(true); //absolute path /** @noinspection PhpUnhandledExceptionInspection */ @@ -698,6 +714,10 @@ public function testGetHomeRelative() { $this->userManager->expects($this->atLeastOnce()) ->method('get') ->willReturn($user); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('ladyofshadows') + ->willReturn(true); /** @noinspection PhpUnhandledExceptionInspection */ $result = $backend->getHome('ladyofshadows'); @@ -727,14 +747,6 @@ public function testGetHomeNoPath() { return false; } }); - $this->access->connection->expects($this->any()) - ->method('getFromCache') - ->willReturnCallback(function ($key) { - if ($key === 'userExistsnewyorker') { - return true; - } - return null; - }); $user = $this->createMock(User::class); $user->expects($this->any()) @@ -746,7 +758,12 @@ public function testGetHomeNoPath() { $this->userManager->expects($this->atLeastOnce()) ->method('get') + ->with('newyorker') ->willReturn($user); + $this->userManager->expects($this->once()) + ->method('exists') + ->with('newyorker') + ->willReturn(true); //no path at all – triggers OC default behaviour $result = $backend->getHome('newyorker'); @@ -785,7 +802,12 @@ public function testGetHomeDeletedUser() { $this->userManager->expects($this->atLeastOnce()) ->method('get') + ->with($uid) ->willReturn($offlineUser); + $this->userManager->expects($this->once()) + ->method('exists') + ->with($uid) + ->willReturn(true); $result = $backend->getHome($uid); $this->assertFalse($result); @@ -885,6 +907,16 @@ public function testGetDisplayName() { } return null; }); + $this->userManager->expects($this->any()) + ->method('exists') + ->willReturnCallback(function ($uid) use ($user1, $user2) { + if ($uid === 'gunslinger') { + return true; + } elseif ($uid === 'newyorker') { + return true; + } + return false; + }); $this->access->expects($this->any()) ->method('getUserMapper') ->willReturn($mapper); @@ -968,6 +1000,16 @@ public function testGetDisplayNamePublicAPI() { } return null; }); + $this->userManager->expects($this->any()) + ->method('exists') + ->willReturnCallback(function ($uid) use ($user1, $user2) { + if ($uid === 'gunslinger') { + return true; + } elseif ($uid === 'newyorker') { + return true; + } + return false; + }); $this->access->expects($this->any()) ->method('getUserMapper') ->willReturn($mapper); @@ -978,7 +1020,7 @@ public function testGetDisplayNamePublicAPI() { }); //with displayName - $result = \OC::$server->getUserManager()->get('gunslinger')->getDisplayName(); + $result = \OC::$server->getUserManager()->get('gunslinger')?->getDisplayName(); $this->assertEquals('Roland Deschain', $result); //empty displayname retrieved @@ -1072,6 +1114,8 @@ public function testLoginName2UserNameSuccess() { ->method('get') ->with($dn) ->willReturn($user); + $this->userManager->expects($this->never()) + ->method('exists'); $this->userManager->expects($this->any()) ->method('getAttributes') ->willReturn(['dn', 'uid', 'mail', 'displayname']); diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 6f18d1156443a..3a535b94e8590 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1124,11 +1124,6 @@ - - - ldapConfigurationActive]]> - - @@ -1145,65 +1140,6 @@ getCode()]]> - - connection->ldapAttributesForGroupSearch]]> - connection->ldapAttributesForUserSearch]]> - connection->ldapBase]]> - connection->ldapBaseGroups]]> - connection->ldapBaseGroups]]> - connection->ldapBaseGroups]]> - connection->ldapBaseUsers]]> - connection->ldapBaseUsers]]> - connection->ldapBaseUsers]]> - connection->ldapBaseUsers]]> - connection->ldapBaseUsers]]> - connection->ldapCacheTTL]]> - connection->ldapCacheTTL]]> - connection->ldapExpertUUIDGroupAttr]]> - connection->ldapExpertUUIDGroupAttr]]> - connection->ldapExpertUUIDUserAttr]]> - connection->ldapExpertUUIDUserAttr]]> - connection->ldapExpertUUIDUserAttr]]> - connection->ldapExpertUsernameAttr]]> - connection->ldapGroupDisplayName]]> - connection->ldapGroupDisplayName]]> - connection->ldapGroupDisplayName]]> - connection->ldapGroupFilter]]> - connection->ldapIgnoreNamingRules]]> - connection->ldapLoginFilter]]> - connection->ldapLoginFilter]]> - connection->ldapPagingSize]]> - connection->ldapPagingSize]]> - connection->ldapPagingSize]]> - connection->ldapPagingSize]]> - connection->ldapPagingSize]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName2]]> - connection->ldapUserFilter]]> - connection->ldapUserFilter]]> - connection->ldapUserFilter]]> - connection->ldapUuidUserAttribute]]> - connection->turnOnPasswordChange]]> - - - - - ldapConfigurationActive]]> - - - - - connection->ldapAdminGroup]]> - - - - - ldapUserAvatarRule]]> - @@ -1214,111 +1150,11 @@ $subj = $key; break;]]> - - configuration->ldapBackupPort]]> - configuration->ldapTLS]]> - - - configuration->ldapAgentName]]> - configuration->ldapAgentName]]> - configuration->ldapAgentName]]> - configuration->ldapAgentPassword]]> - configuration->ldapAgentPassword]]> - configuration->ldapAgentPassword]]> - configuration->ldapBackupPort]]> - configuration->ldapBase]]> - configuration->ldapBase]]> - configuration->ldapBaseGroups]]> - configuration->ldapBaseUsers]]> - configuration->ldapCacheTTL]]> - configuration->ldapCacheTTL]]> - configuration->ldapConfigurationActive]]> - configuration->ldapConfigurationActive]]> - configuration->ldapConfigurationActive]]> - configuration->ldapConnectionTimeout]]> - configuration->ldapHost]]> - configuration->ldapHost]]> - configuration->ldapLoginFilter]]> - configuration->ldapOverrideMainServer]]> - configuration->ldapPort]]> - configuration->ldapTLS]]> - configuration->ldapTLS]]> - configuration->turnOffCertCheck]]> - - - - - ldapConfigurationActive]]> - - - - - access->connection->ldapLoginFilter), - $this->access->combineFilterWithAnd([ - $this->access->getFilterPartForUserSearch($search), - $this->access->connection->ldapUserFilter - ]) - ]]]> - access->connection->ldapLoginFilter), - $this->access->getFilterPartForUserSearch($search) - ]]]> - - - access->connection->ldapMatchingRuleInChainState]]> - access->connection->ldapMatchingRuleInChainState]]> - - - access->connection->hasMemberOfFilterSupport]]> - access->connection->hasMemberOfFilterSupport]]> - access->connection->ldapAdminGroup]]> - access->connection->ldapBaseGroups]]> - access->connection->ldapDynamicGroupMemberURL]]> - access->connection->ldapDynamicGroupMemberURL]]> - access->connection->ldapGidNumber]]> - access->connection->ldapGidNumber]]> - access->connection->ldapGidNumber]]> - access->connection->ldapGroupDisplayName]]> - access->connection->ldapGroupDisplayName]]> - access->connection->ldapGroupDisplayName]]> - access->connection->ldapGroupDisplayName]]> - access->connection->ldapGroupDisplayName]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupFilter]]> - access->connection->ldapGroupMemberAssocAttr]]> - access->connection->ldapGroupMemberAssocAttr]]> - access->connection->ldapGroupMemberAssocAttr]]> - access->connection->ldapHost]]> - access->connection->ldapLoginFilter]]> - access->connection->ldapLoginFilter]]> - access->connection->ldapLoginFilter]]> - access->connection->ldapMatchingRuleInChainState]]> - access->connection->ldapMatchingRuleInChainState]]> - access->connection->ldapNestedGroups]]> - access->connection->ldapNestedGroups]]> - access->connection->ldapNestedGroups]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserFilter]]> - access->connection->ldapUserFilter]]> - access->connection->ldapUserFilter]]> - access->connection->useMemberOfToDetectMembership]]> - access->connection->useMemberOfToDetectMembership]]> - @@ -1328,11 +1164,6 @@ refBackend = &$this->backends[$configPrefix]]]> - - - connection->ldapExtStorageHomeAttribute]]> - - @@ -1340,12 +1171,6 @@ - - connection->ldapUserDisplayName]]> - connection->ldapUserFilter]]> - ldapPagingSize]]> - ldapPagingSize]]> - @@ -1364,24 +1189,6 @@ - - access->getConnection()->homeFolderNamingRule]]> - access->getConnection()->ldapAttributeAddress]]> - access->getConnection()->ldapAttributeBiography]]> - access->getConnection()->ldapAttributeFediverse]]> - access->getConnection()->ldapAttributeHeadline]]> - access->getConnection()->ldapAttributeOrganisation]]> - access->getConnection()->ldapAttributePhone]]> - access->getConnection()->ldapAttributeRole]]> - access->getConnection()->ldapAttributeTwitter]]> - access->getConnection()->ldapAttributeWebsite]]> - access->getConnection()->ldapEmailAttribute]]> - access->getConnection()->ldapExpertUUIDUserAttr]]> - access->getConnection()->ldapExtStorageHomeAttribute]]> - access->getConnection()->ldapQuotaAttribute]]> - access->getConnection()->ldapUserDisplayName]]> - access->getConnection()->ldapUserDisplayName2]]> - @@ -1396,31 +1203,6 @@ 0)]]> - - access->connection->homeFolderNamingRule]]> - connection->homeFolderNamingRule]]> - connection->ldapAttributeAddress]]> - connection->ldapAttributeBiography]]> - connection->ldapAttributeFediverse]]> - connection->ldapAttributeHeadline]]> - connection->ldapAttributeOrganisation]]> - connection->ldapAttributePhone]]> - connection->ldapAttributeRole]]> - connection->ldapAttributeTwitter]]> - connection->ldapAttributeWebsite]]> - connection->ldapDefaultPPolicyDN]]> - connection->ldapEmailAttribute]]> - connection->ldapEmailAttribute]]> - connection->ldapExtStorageHomeAttribute]]> - connection->ldapExtStorageHomeAttribute]]> - connection->ldapQuotaAttribute]]> - connection->ldapQuotaAttribute]]> - connection->ldapQuotaDefault]]> - connection->ldapQuotaDefault]]> - connection->ldapUserDisplayName]]> - connection->ldapUserDisplayName2]]> - connection->turnOnPasswordChange]]> - @@ -1437,29 +1219,11 @@ 0)]]> - - access->connection->ldapDefaultPPolicyDN]]> - access->connection->ldapHost]]> - access->connection->ldapHost]]> - access->connection->ldapPort]]> - access->connection->ldapUserAvatarRule]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserDisplayName]]> - access->connection->ldapUserDisplayName2]]> - access->connection->ldapUserFilter]]> - access->connection->ldapUserFilter]]> - access->connection->markRemnantsAsDisabled]]> - access->connection->turnOnPasswordChange]]> - access->connection->turnOnPasswordChange]]> - - - getAccess(array_key_first($this->backends) ?? '')->connection->markRemnantsAsDisabled]]> - refBackend = &$this->backends[$configPrefix]]]> @@ -1468,51 +1232,6 @@ - - configuration->hasMemberOfFilterSupport]]> - - - access->connection->ldapLoginFilter]]> - configuration->hasMemberOfFilterSupport]]> - configuration->ldapAgentName]]> - configuration->ldapAgentName]]> - configuration->ldapAgentName]]> - configuration->ldapAgentName]]> - configuration->ldapAgentPassword]]> - configuration->ldapAgentPassword]]> - configuration->ldapAgentPassword]]> - configuration->ldapBase]]> - configuration->ldapBase]]> - configuration->ldapBase]]> - configuration->ldapBase]]> - configuration->ldapBaseGroups]]> - configuration->ldapEmailAttribute]]> - configuration->ldapGroupDisplayName]]> - configuration->ldapGroupFilter]]> - configuration->ldapGroupFilter]]> - configuration->ldapGroupFilterGroups]]> - configuration->ldapGroupFilterObjectclass]]> - configuration->ldapHost]]> - configuration->ldapHost]]> - configuration->ldapHost]]> - configuration->ldapHost]]> - configuration->ldapHost]]> - configuration->ldapLoginFilterAttributes]]> - configuration->ldapLoginFilterAttributes]]> - configuration->ldapLoginFilterEmail]]> - configuration->ldapLoginFilterUsername]]> - configuration->ldapPort]]> - configuration->ldapPort]]> - configuration->ldapTLS]]> - configuration->ldapUserDisplayName]]> - configuration->ldapUserDisplayName]]> - configuration->ldapUserFilter]]> - configuration->ldapUserFilter]]> - configuration->ldapUserFilter]]> - configuration->ldapUserFilter]]> - configuration->ldapUserFilterGroups]]> - configuration->ldapUserFilterObjectclass]]> - @@ -1654,11 +1373,6 @@ - - - - - @@ -1680,11 +1394,6 @@ - - - __get('ldapLoginFilterEmail')]]> - - getCode()]]> @@ -1836,9 +1545,7 @@ - getOverwriteHost()]]> - cookies[$key]) ? $this->cookies[$key] : null]]> env[$key]) ? $this->env[$key] : null]]> files[$key]) ? $this->files[$key] : null]]> @@ -1849,16 +1556,6 @@ - - method]]> - method]]> - method]]> - method]]> - method]]> - parameters]]> - server]]> - server]]> - @@ -2555,7 +2252,6 @@ - @@ -2705,10 +2401,6 @@ - - cachedGroups]]> - cachedUserGroups]]> - @@ -2889,11 +2581,6 @@ - - - - - @@ -3231,9 +2918,6 @@ - - cachedUsers]]> - diff --git a/core/Command/Preview/Generate.php b/core/Command/Preview/Generate.php index 865283191993b..b43f52a66a80b 100644 --- a/core/Command/Preview/Generate.php +++ b/core/Command/Preview/Generate.php @@ -57,13 +57,13 @@ protected function configure() { protected function execute(InputInterface $input, OutputInterface $output): int { $fileInput = $input->getArgument("file"); $sizes = $input->getOption("size"); - $sizes = array_map(function (string $size) use ($output, &$error) { + $sizes = array_map(function (string $size) use ($output) { if (str_contains($size, 'x')) { $sizeParts = explode('x', $size, 2); } else { $sizeParts = [$size, $size]; } - if (!is_numeric($sizeParts[0]) || !is_numeric($sizeParts[1])) { + if (!is_numeric($sizeParts[0]) || !is_numeric($sizeParts[1] ?? null)) { $output->writeln("Invalid size $size"); return null; } diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 7a614878ab55e..af3993df8bddc 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -57,12 +57,12 @@ * Class for accessing variables in the request. * This class provides an immutable object with request variables. * - * @property mixed[] cookies - * @property mixed[] env - * @property mixed[] files - * @property string method - * @property mixed[] parameters - * @property mixed[] server + * @property mixed[] $cookies + * @property mixed[] $env + * @property mixed[] $files + * @property string $method + * @property mixed[] $parameters + * @property mixed[] $server * @template-implements \ArrayAccess */ class Request implements \ArrayAccess, \Countable, IRequest { diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 83aed4381b342..4f2cc53688bfa 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -228,8 +228,8 @@ public function offsetGet($id) { /** * @deprecated 20.0.0 use \OCP\IContainer::registerService */ - public function offsetSet($id, $service): void { - $this->container->offsetSet($id, $service); + public function offsetSet($offset, $value): void { + $this->container->offsetSet($offset, $value); } /** diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 8fec24996891f..4e3dcf1de51cf 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -526,6 +526,7 @@ public function getSourcePath($path) { $realPath = realpath($pathToResolve); while ($realPath === false) { // for non existing files check the parent directory $currentPath = dirname($currentPath); + /** @psalm-suppress TypeDoesNotContainType Let's be extra cautious and still check for empty string */ if ($currentPath === '' || $currentPath === '.') { return $fullPath; } diff --git a/lib/private/Group/Manager.php b/lib/private/Group/Manager.php index eeff2f35c45e5..694b6de30c4e8 100644 --- a/lib/private/Group/Manager.php +++ b/lib/private/Group/Manager.php @@ -101,26 +101,15 @@ public function __construct(\OC\User\Manager $userManager, $this->logger = $logger; $this->displayNameCache = new DisplayNameCache($cacheFactory, $this); - $cachedGroups = &$this->cachedGroups; - $cachedUserGroups = &$this->cachedUserGroups; - $this->listen('\OC\Group', 'postDelete', function ($group) use (&$cachedGroups, &$cachedUserGroups) { - /** - * @var \OC\Group\Group $group - */ - unset($cachedGroups[$group->getGID()]); - $cachedUserGroups = []; + $this->listen('\OC\Group', 'postDelete', function (IGroup $group): void { + unset($this->cachedGroups[$group->getGID()]); + $this->cachedUserGroups = []; }); - $this->listen('\OC\Group', 'postAddUser', function ($group) use (&$cachedUserGroups) { - /** - * @var \OC\Group\Group $group - */ - $cachedUserGroups = []; + $this->listen('\OC\Group', 'postAddUser', function (IGroup $group): void { + $this->cachedUserGroups = []; }); - $this->listen('\OC\Group', 'postRemoveUser', function ($group) use (&$cachedUserGroups) { - /** - * @var \OC\Group\Group $group - */ - $cachedUserGroups = []; + $this->listen('\OC\Group', 'postRemoveUser', function (IGroup $group): void { + $this->cachedUserGroups = []; }); } diff --git a/lib/private/Preview/MimeIconProvider.php b/lib/private/Preview/MimeIconProvider.php index 80073c307c9f9..d0c484d20a554 100644 --- a/lib/private/Preview/MimeIconProvider.php +++ b/lib/private/Preview/MimeIconProvider.php @@ -47,7 +47,7 @@ public function getMimeIconUrl(string $mime): null|string { $aliases = $this->mimetypeDetector->getAllAliases(); // Remove comments - $aliases = array_filter($aliases, static function ($key) { + $aliases = array_filter($aliases, static function (string $key) { return !($key === '' || $key[0] === '_'); }, ARRAY_FILTER_USE_KEY); diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 7067afc273b90..fee66f9989447 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -79,34 +79,26 @@ class Manager extends PublicEmitter implements IUserManager { /** * @var \OCP\UserInterface[] $backends */ - private $backends = []; + private array $backends = []; /** - * @var \OC\User\User[] $cachedUsers + * @var array $cachedUsers */ - private $cachedUsers = []; + private array $cachedUsers = []; - /** @var IConfig */ - private $config; - - /** @var ICache */ - private $cache; - - /** @var IEventDispatcher */ - private $eventDispatcher; + private ICache $cache; private DisplayNameCache $displayNameCache; - public function __construct(IConfig $config, + public function __construct( + private IConfig $config, ICacheFactory $cacheFactory, - IEventDispatcher $eventDispatcher, - private LoggerInterface $logger) { - $this->config = $config; + private IEventDispatcher $eventDispatcher, + private LoggerInterface $logger, + ) { $this->cache = new WithLocalCache($cacheFactory->createDistributed('user_backend_map')); - $cachedUsers = &$this->cachedUsers; - $this->listen('\OC\User', 'postDelete', function ($user) use (&$cachedUsers) { - /** @var \OC\User\User $user */ - unset($cachedUsers[$user->getUID()]); + $this->listen('\OC\User', 'postDelete', function (IUser $user): void { + unset($this->cachedUsers[$user->getUID()]); }); $this->eventDispatcher = $eventDispatcher; $this->displayNameCache = new DisplayNameCache($cacheFactory, $this);