summaryrefslogtreecommitdiffstats
path: root/lib/private/User
diff options
context:
space:
mode:
authorCarl Schwan <carl@carlschwan.eu>2022-08-15 15:28:30 +0200
committerGitHub <noreply@github.com>2022-08-15 15:28:30 +0200
commit51b9847fad73a1ca67dbf504358d90bd8f9e71d8 (patch)
treeb104cf1c540dd1dd195ca5fd30c42b888012cbab /lib/private/User
parent6d6662ec68c8e15c4c6bfdf1c694794badd412d7 (diff)
parentcb97e8f15c75cc46e345ebfc79dcad1b9c48bd01 (diff)
downloadnextcloud-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.php8
-rw-r--r--lib/private/User/LazyUser.php3
-rw-r--r--lib/private/User/Listeners/UserChangedListener.php62
-rw-r--r--lib/private/User/Listeners/UserDeletedListener.php65
-rw-r--r--lib/private/User/Manager.php30
-rw-r--r--lib/private/User/Session.php10
-rw-r--r--lib/private/User/User.php50
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;