diff options
author | Carl Schwan <carl@carlschwan.eu> | 2022-08-15 15:28:30 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-15 15:28:30 +0200 |
commit | 51b9847fad73a1ca67dbf504358d90bd8f9e71d8 (patch) | |
tree | b104cf1c540dd1dd195ca5fd30c42b888012cbab /lib/private/User | |
parent | 6d6662ec68c8e15c4c6bfdf1c694794badd412d7 (diff) | |
parent | cb97e8f15c75cc46e345ebfc79dcad1b9c48bd01 (diff) | |
download | nextcloud-server-51b9847fad73a1ca67dbf504358d90bd8f9e71d8.tar.gz nextcloud-server-51b9847fad73a1ca67dbf504358d90bd8f9e71d8.zip |
Merge branch 'master' into display-name-cache-public
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Diffstat (limited to 'lib/private/User')
-rw-r--r-- | lib/private/User/Database.php | 8 | ||||
-rw-r--r-- | lib/private/User/LazyUser.php | 3 | ||||
-rw-r--r-- | lib/private/User/Listeners/UserChangedListener.php | 62 | ||||
-rw-r--r-- | lib/private/User/Listeners/UserDeletedListener.php | 65 | ||||
-rw-r--r-- | lib/private/User/Manager.php | 30 | ||||
-rw-r--r-- | lib/private/User/Session.php | 10 | ||||
-rw-r--r-- | lib/private/User/User.php | 50 |
7 files changed, 186 insertions, 42 deletions
diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index a9464c27085..0b38f04bfe3 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -45,7 +45,7 @@ declare(strict_types=1); */ namespace OC\User; -use OC\Cache\CappedMemoryCache; +use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; use OCP\Security\Events\ValidatePasswordPolicyEvent; @@ -215,6 +215,10 @@ class Database extends ABackend implements * Change the display name of a user */ public function setDisplayName(string $uid, string $displayName): bool { + if (mb_strlen($displayName) > 64) { + return false; + } + $this->fixDI(); if ($this->userExists($uid)) { @@ -275,7 +279,7 @@ class Database extends ABackend implements ->setMaxResults($limit) ->setFirstResult($offset); - $result = $query->execute(); + $result = $query->executeQuery(); $displayNames = []; while ($row = $result->fetch()) { $displayNames[(string)$row['uid']] = (string)$row['displayname']; diff --git a/lib/private/User/LazyUser.php b/lib/private/User/LazyUser.php index 30e44d57061..118dd3d0699 100644 --- a/lib/private/User/LazyUser.php +++ b/lib/private/User/LazyUser.php @@ -25,6 +25,7 @@ namespace OC\User; use OCP\IUser; use OCP\IUserManager; +use OCP\UserInterface; class LazyUser implements IUser { private ?IUser $user = null; @@ -81,7 +82,7 @@ class LazyUser implements IUser { return $this->getUser()->getBackendClassName(); } - public function getBackend() { + public function getBackend(): ?UserInterface { return $this->getUser()->getBackend(); } diff --git a/lib/private/User/Listeners/UserChangedListener.php b/lib/private/User/Listeners/UserChangedListener.php new file mode 100644 index 00000000000..a561db2423d --- /dev/null +++ b/lib/private/User/Listeners/UserChangedListener.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\User\Listeners; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\UserChangedEvent; +use OCP\Files\NotFoundException; +use OCP\IAvatarManager; + +/** + * @template-implements IEventListener<UserChangedEvent> + */ +class UserChangedListener implements IEventListener { + private IAvatarManager $avatarManager; + + public function __construct(IAvatarManager $avatarManager) { + $this->avatarManager = $avatarManager; + } + + public function handle(Event $event): void { + if (!($event instanceof UserChangedEvent)) { + return; + } + + $user = $event->getUser(); + $feature = $event->getFeature(); + $oldValue = $event->getOldValue(); + $value = $event->getValue(); + + // We only change the avatar on display name changes + if ($feature === 'displayName') { + try { + $avatar = $this->avatarManager->getAvatar($user->getUID()); + $avatar->userChanged($feature, $oldValue, $value); + } catch (NotFoundException $e) { + // no avatar to remove + } + } + } +} diff --git a/lib/private/User/Listeners/UserDeletedListener.php b/lib/private/User/Listeners/UserDeletedListener.php new file mode 100644 index 00000000000..7c9c46ef371 --- /dev/null +++ b/lib/private/User/Listeners/UserDeletedListener.php @@ -0,0 +1,65 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Carl Schwan <carl@carlschwan.eu> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\User\Listeners; + +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\UserDeletedEvent; +use OCP\Files\NotFoundException; +use OCP\IAvatarManager; +use Psr\Log\LoggerInterface; + +/** + * @template-implements IEventListener<UserDeletedEvent> + */ +class UserDeletedListener implements IEventListener { + private IAvatarManager $avatarManager; + private LoggerInterface $logger; + + public function __construct(LoggerInterface $logger, IAvatarManager $avatarManager) { + $this->avatarManager = $avatarManager; + $this->logger = $logger; + } + + public function handle(Event $event): void { + if (!($event instanceof UserDeletedEvent)) { + return; + } + + $user = $event->getUser(); + + // Delete avatar on user deletion + try { + $avatar = $this->avatarManager->getAvatar($user->getUID()); + $avatar->remove(true); + } catch (NotFoundException $e) { + // no avatar to remove + } catch (\Exception $e) { + // Ignore exceptions + $this->logger->info('Could not cleanup avatar of ' . $user->getUID(), [ + 'exception' => $e, + ]); + } + } +} diff --git a/lib/private/User/Manager.php b/lib/private/User/Manager.php index 5a5edbdbd27..55ac663f3ec 100644 --- a/lib/private/User/Manager.php +++ b/lib/private/User/Manager.php @@ -48,6 +48,8 @@ use OCP\Notification\IManager; use OCP\Support\Subscription\IRegistry; use OCP\User\Backend\IGetRealUIDBackend; use OCP\User\Backend\ISearchKnownUsersBackend; +use OCP\User\Backend\ICheckPasswordBackend; +use OCP\User\Backend\ICountUsersBackend; use OCP\User\Events\BeforeUserCreatedEvent; use OCP\User\Events\UserCreatedEvent; use OCP\UserInterface; @@ -230,7 +232,7 @@ class Manager extends PublicEmitter implements IUserManager { * * @param string $loginName * @param string $password - * @return mixed the User object on success, false otherwise + * @return IUser|false the User object on success, false otherwise */ public function checkPassword($loginName, $password) { $result = $this->checkPasswordNoLogging($loginName, $password); @@ -261,7 +263,8 @@ class Manager extends PublicEmitter implements IUserManager { $backends = $this->backends; } foreach ($backends as $backend) { - if ($backend->implementsActions(Backend::CHECK_PASSWORD)) { + if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) { + /** @var ICheckPasswordBackend $backend */ $uid = $backend->checkPassword($loginName, $password); if ($uid !== false) { return $this->getUserObject($uid, $backend); @@ -275,7 +278,8 @@ class Manager extends PublicEmitter implements IUserManager { $password = urldecode($password); foreach ($backends as $backend) { - if ($backend->implementsActions(Backend::CHECK_PASSWORD)) { + if ($backend instanceof ICheckPasswordBackend || $backend->implementsActions(Backend::CHECK_PASSWORD)) { + /** @var ICheckPasswordBackend|UserInterface $backend */ $uid = $backend->checkPassword($loginName, $password); if ($uid !== false) { return $this->getUserObject($uid, $backend); @@ -383,7 +387,7 @@ class Manager extends PublicEmitter implements IUserManager { * @param string $uid * @param string $password * @throws \InvalidArgumentException - * @return bool|IUser the created user or false + * @return false|IUser the created user or false */ public function createUser($uid, $password) { // DI injection is not used here as IRegistry needs the user manager itself for user count and thus it would create a cyclic dependency @@ -422,7 +426,7 @@ class Manager extends PublicEmitter implements IUserManager { * @param string $uid * @param string $password * @param UserInterface $backend - * @return IUser|null + * @return IUser|false * @throws \InvalidArgumentException */ public function createUserFromBackend($uid, $password, UserInterface $backend) { @@ -476,8 +480,9 @@ class Manager extends PublicEmitter implements IUserManager { /** @deprecated 21.0.0 use UserCreatedEvent event with the IEventDispatcher instead */ $this->emit('\OC\User', 'postCreateUser', [$user, $password]); $this->eventDispatcher->dispatchTyped(new UserCreatedEvent($user, $password)); + return $user; } - return $user; + return false; } /** @@ -485,16 +490,13 @@ class Manager extends PublicEmitter implements IUserManager { * * @param boolean $hasLoggedIn when true only users that have a lastLogin * entry in the preferences table will be affected - * @return array|int an array of backend class as key and count number as value - * if $hasLoggedIn is true only an int is returned + * @return array<string, int> an array of backend class as key and count number as value */ - public function countUsers($hasLoggedIn = false) { - if ($hasLoggedIn) { - return $this->countSeenUsers(); - } + public function countUsers() { $userCountStatistics = []; foreach ($this->backends as $backend) { - if ($backend->implementsActions(Backend::COUNT_USERS)) { + if ($backend instanceof ICountUsersBackend || $backend->implementsActions(Backend::COUNT_USERS)) { + /** @var ICountUsersBackend|IUserBackend $backend */ $backendUsers = $backend->countUsers(); if ($backendUsers !== false) { if ($backend instanceof IUserBackend) { @@ -535,7 +537,7 @@ class Manager extends PublicEmitter implements IUserManager { * The callback is executed for each user on each backend. * If the callback returns false no further users will be retrieved. * - * @param \Closure $callback + * @psalm-param \Closure(\OCP\IUser):?bool $callback * @param string $search * @param boolean $onlySeen when true only users that have a lastLogin entry * in the preferences table will be affected diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 365a01c4595..626ddca2dad 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -90,7 +90,7 @@ use Symfony\Component\EventDispatcher\GenericEvent; */ class Session implements IUserSession, Emitter { - /** @var Manager|PublicEmitter $manager */ + /** @var Manager $manager */ private $manager; /** @var ISession $session */ @@ -288,9 +288,9 @@ class Session implements IUserSession, Emitter { } /** - * get the login name of the current user + * Get the login name of the current user * - * @return string + * @return ?string */ public function getLoginName() { if ($this->activeUser) { @@ -870,7 +870,7 @@ class Session implements IUserSession, Emitter { // replace successfully used token with a new one $this->config->deleteUserValue($uid, 'login_token', $currentToken); $newToken = $this->random->generate(32); - $this->config->setUserValue($uid, 'login_token', $newToken, $this->timeFactory->getTime()); + $this->config->setUserValue($uid, 'login_token', $newToken, (string)$this->timeFactory->getTime()); try { $sessionId = $this->session->getId(); @@ -905,7 +905,7 @@ class Session implements IUserSession, Emitter { */ public function createRememberMeToken(IUser $user) { $token = $this->random->generate(32); - $this->config->setUserValue($user->getUID(), 'login_token', $token, $this->timeFactory->getTime()); + $this->config->setUserValue($user->getUID(), 'login_token', $token, (string)$this->timeFactory->getTime()); $this->setMagicInCookie($user->getUID(), $token); } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index e7aa72fafba..7f7d6273e30 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -52,6 +52,10 @@ use OCP\IUserBackend; use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\UserDeletedEvent; use OCP\User\GetQuotaEvent; +use OCP\User\Backend\ISetDisplayNameBackend; +use OCP\User\Backend\ISetPasswordBackend; +use OCP\User\Backend\IProvideAvatarBackend; +use OCP\User\Backend\IGetHomeBackend; use OCP\UserInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -155,7 +159,9 @@ class User implements IUser { $displayName = trim($displayName); $oldDisplayName = $this->getDisplayName(); if ($this->backend->implementsActions(Backend::SET_DISPLAYNAME) && !empty($displayName) && $displayName !== $oldDisplayName) { - $result = $this->backend->setDisplayName($this->uid, $displayName); + /** @var ISetDisplayNameBackend $backend */ + $backend = $this->backend; + $result = $backend->setDisplayName($this->uid, $displayName); if ($result) { $this->displayName = $displayName; $this->triggerChange('displayName', $displayName, $oldDisplayName); @@ -238,10 +244,15 @@ class User implements IUser { * updates the timestamp of the most recent login of this user */ public function updateLastLoginTimestamp() { - $firstTimeLogin = ($this->getLastLogin() === 0); - $this->lastLogin = time(); - $this->config->setUserValue( - $this->uid, 'login', 'lastLogin', $this->lastLogin); + $previousLogin = $this->getLastLogin(); + $now = time(); + $firstTimeLogin = $previousLogin === 0; + + if ($now - $previousLogin > 60) { + $this->lastLogin = time(); + $this->config->setUserValue( + $this->uid, 'login', 'lastLogin', (string)$this->lastLogin); + } return $firstTimeLogin; } @@ -280,7 +291,7 @@ class User implements IUser { \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid); \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); - /** @var IAvatarManager $avatarManager */ + /** @var AvatarManager $avatarManager */ $avatarManager = \OC::$server->query(AvatarManager::class); $avatarManager->deleteUserAvatar($this->uid); @@ -319,7 +330,9 @@ class User implements IUser { $this->emitter->emit('\OC\User', 'preSetPassword', [$this, $password, $recoveryPassword]); } if ($this->backend->implementsActions(Backend::SET_PASSWORD)) { - $result = $this->backend->setPassword($this->uid, $password); + /** @var ISetPasswordBackend $backend */ + $backend = $this->backend; + $result = $backend->setPassword($this->uid, $password); if ($result !== false) { $this->legacyDispatcher->dispatch(IUser::class . '::postSetPassword', new GenericEvent($this, [ @@ -344,7 +357,8 @@ class User implements IUser { */ public function getHome() { if (!$this->home) { - if ($this->backend->implementsActions(Backend::GET_HOME) and $home = $this->backend->getHome($this->uid)) { + /** @psalm-suppress UndefinedInterfaceMethod Once we get rid of the legacy implementsActions, psalm won't complain anymore */ + if (($this->backend instanceof IGetHomeBackend || $this->backend->implementsActions(Backend::GET_HOME)) && $home = $this->backend->getHome($this->uid)) { $this->home = $home; } elseif ($this->config) { $this->home = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data') . '/' . $this->uid; @@ -367,18 +381,20 @@ class User implements IUser { return get_class($this->backend); } - public function getBackend() { + public function getBackend(): ?UserInterface { return $this->backend; } /** - * check if the backend allows the user to change his avatar on Personal page + * Check if the backend allows the user to change his avatar on Personal page * * @return bool */ public function canChangeAvatar() { - if ($this->backend->implementsActions(Backend::PROVIDE_AVATAR)) { - return $this->backend->canChangeAvatar($this->uid); + if ($this->backend instanceof IProvideAvatarBackend || $this->backend->implementsActions(Backend::PROVIDE_AVATAR)) { + /** @var IProvideAvatarBackend $backend */ + $backend = $this->backend; + return $backend->canChangeAvatar($this->uid); } return true; } @@ -501,7 +517,7 @@ class User implements IUser { $oldQuota = $this->config->getUserValue($this->uid, 'files', 'quota', ''); if ($quota !== 'none' and $quota !== 'default') { $quota = OC_Helper::computerFileSize($quota); - $quota = OC_Helper::humanFileSize($quota); + $quota = OC_Helper::humanFileSize((int)$quota); } if ($quota !== $oldQuota) { $this->config->setUserValue($this->uid, 'files', 'quota', $quota); @@ -544,15 +560,9 @@ class User implements IUser { return $uid . '@' . $server; } - /** - * @param string $url - * @return string - */ - private function removeProtocolFromUrl($url) { + private function removeProtocolFromUrl(string $url): string { if (strpos($url, 'https://') === 0) { return substr($url, strlen('https://')); - } elseif (strpos($url, 'http://') === 0) { - return substr($url, strlen('http://')); } return $url; |