aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Authentication
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Authentication')
-rw-r--r--lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php2
-rw-r--r--lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php28
-rw-r--r--lib/private/Authentication/Login/Chain.php75
-rw-r--r--lib/private/Authentication/Login/EmailLoginCommand.php53
-rw-r--r--lib/private/Authentication/Login/FlowV2EphemeralSessionsCommand.php30
-rw-r--r--lib/private/Authentication/Login/WebAuthnChain.php2
-rw-r--r--lib/private/Authentication/LoginCredentials/Credentials.php1
-rw-r--r--lib/private/Authentication/LoginCredentials/Store.php19
-rw-r--r--lib/private/Authentication/Notifications/Notifier.php6
-rw-r--r--lib/private/Authentication/Token/IProvider.php4
-rw-r--r--lib/private/Authentication/Token/Manager.php7
-rw-r--r--lib/private/Authentication/Token/PublicKeyToken.php15
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenMapper.php31
-rw-r--r--lib/private/Authentication/Token/PublicKeyTokenProvider.php49
-rw-r--r--lib/private/Authentication/Token/RemoteWipe.php4
-rw-r--r--lib/private/Authentication/Token/TokenCleanupJob.php1
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php8
-rw-r--r--lib/private/Authentication/TwoFactorAuth/Manager.php18
-rw-r--r--lib/private/Authentication/TwoFactorAuth/ProviderLoader.php30
-rw-r--r--lib/private/Authentication/WebAuthn/CredentialRepository.php9
-rw-r--r--lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php11
-rw-r--r--lib/private/Authentication/WebAuthn/Manager.php25
22 files changed, 208 insertions, 220 deletions
diff --git a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
index d95bcd98cf9..5781c1edf16 100644
--- a/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
+++ b/lib/private/Authentication/Listeners/RemoteWipeNotificationsListener.php
@@ -45,7 +45,7 @@ class RemoteWipeNotificationsListener implements IEventListener {
$notification->setApp('auth')
->setUser($token->getUID())
->setDateTime($this->timeFactory->getDateTime())
- ->setObject('token', (string) $token->getId())
+ ->setObject('token', (string)$token->getId())
->setSubject($event, [
'name' => $token->getName(),
]);
diff --git a/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php b/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
index 697aea71c6d..a619021d192 100644
--- a/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
+++ b/lib/private/Authentication/Listeners/UserDeletedFilesCleanupListener.php
@@ -16,30 +16,34 @@ use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\Storage\IStorage;
use OCP\User\Events\BeforeUserDeletedEvent;
use OCP\User\Events\UserDeletedEvent;
+use Psr\Log\LoggerInterface;
/** @template-implements IEventListener<BeforeUserDeletedEvent|UserDeletedEvent> */
class UserDeletedFilesCleanupListener implements IEventListener {
/** @var array<string,IStorage> */
private $homeStorageCache = [];
- /** @var IMountProviderCollection */
- private $mountProviderCollection;
-
- public function __construct(IMountProviderCollection $mountProviderCollection) {
- $this->mountProviderCollection = $mountProviderCollection;
+ public function __construct(
+ private IMountProviderCollection $mountProviderCollection,
+ private LoggerInterface $logger,
+ ) {
}
public function handle(Event $event): void {
+ $user = $event->getUser();
+
// since we can't reliably get the user home storage after the user is deleted
// but the user deletion might get canceled during the before event
// we only cache the user home storage during the before event and then do the
// action deletion during the after event
if ($event instanceof BeforeUserDeletedEvent) {
- $userHome = $this->mountProviderCollection->getHomeMountForUser($event->getUser());
+ $this->logger->debug('Prepare deleting storage for user {userId}', ['userId' => $user->getUID()]);
+
+ $userHome = $this->mountProviderCollection->getHomeMountForUser($user);
$storage = $userHome->getStorage();
if (!$storage) {
- throw new \Exception("Account has no home storage");
+ throw new \Exception('Account has no home storage');
}
// remove all wrappers, so we do the delete directly on the home storage bypassing any wrapper
@@ -51,16 +55,18 @@ class UserDeletedFilesCleanupListener implements IEventListener {
$this->homeStorageCache[$event->getUser()->getUID()] = $storage;
}
if ($event instanceof UserDeletedEvent) {
- if (!isset($this->homeStorageCache[$event->getUser()->getUID()])) {
- throw new \Exception("UserDeletedEvent fired without matching BeforeUserDeletedEvent");
+ if (!isset($this->homeStorageCache[$user->getUID()])) {
+ throw new \Exception('UserDeletedEvent fired without matching BeforeUserDeletedEvent');
}
- $storage = $this->homeStorageCache[$event->getUser()->getUID()];
+ $storage = $this->homeStorageCache[$user->getUID()];
$cache = $storage->getCache();
$storage->rmdir('');
+ $this->logger->debug('Deleted storage for user {userId}', ['userId' => $user->getUID()]);
+
if ($cache instanceof Cache) {
$cache->clear();
} else {
- throw new \Exception("Home storage has invalid cache");
+ throw new \Exception('Home storage has invalid cache');
}
}
}
diff --git a/lib/private/Authentication/Login/Chain.php b/lib/private/Authentication/Login/Chain.php
index abd24287a6c..fc90d9225a7 100644
--- a/lib/private/Authentication/Login/Chain.php
+++ b/lib/private/Authentication/Login/Chain.php
@@ -9,67 +9,20 @@ declare(strict_types=1);
namespace OC\Authentication\Login;
class Chain {
- /** @var PreLoginHookCommand */
- private $preLoginHookCommand;
-
- /** @var UserDisabledCheckCommand */
- private $userDisabledCheckCommand;
-
- /** @var UidLoginCommand */
- private $uidLoginCommand;
-
- /** @var EmailLoginCommand */
- private $emailLoginCommand;
-
- /** @var LoggedInCheckCommand */
- private $loggedInCheckCommand;
-
- /** @var CompleteLoginCommand */
- private $completeLoginCommand;
-
- /** @var CreateSessionTokenCommand */
- private $createSessionTokenCommand;
-
- /** @var ClearLostPasswordTokensCommand */
- private $clearLostPasswordTokensCommand;
-
- /** @var UpdateLastPasswordConfirmCommand */
- private $updateLastPasswordConfirmCommand;
-
- /** @var SetUserTimezoneCommand */
- private $setUserTimezoneCommand;
-
- /** @var TwoFactorCommand */
- private $twoFactorCommand;
-
- /** @var FinishRememberedLoginCommand */
- private $finishRememberedLoginCommand;
-
- public function __construct(PreLoginHookCommand $preLoginHookCommand,
- UserDisabledCheckCommand $userDisabledCheckCommand,
- UidLoginCommand $uidLoginCommand,
- EmailLoginCommand $emailLoginCommand,
- LoggedInCheckCommand $loggedInCheckCommand,
- CompleteLoginCommand $completeLoginCommand,
- CreateSessionTokenCommand $createSessionTokenCommand,
- ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
- UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
- SetUserTimezoneCommand $setUserTimezoneCommand,
- TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ public function __construct(
+ private PreLoginHookCommand $preLoginHookCommand,
+ private UserDisabledCheckCommand $userDisabledCheckCommand,
+ private UidLoginCommand $uidLoginCommand,
+ private LoggedInCheckCommand $loggedInCheckCommand,
+ private CompleteLoginCommand $completeLoginCommand,
+ private CreateSessionTokenCommand $createSessionTokenCommand,
+ private ClearLostPasswordTokensCommand $clearLostPasswordTokensCommand,
+ private UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
+ private SetUserTimezoneCommand $setUserTimezoneCommand,
+ private TwoFactorCommand $twoFactorCommand,
+ private FinishRememberedLoginCommand $finishRememberedLoginCommand,
+ private FlowV2EphemeralSessionsCommand $flowV2EphemeralSessionsCommand,
) {
- $this->preLoginHookCommand = $preLoginHookCommand;
- $this->userDisabledCheckCommand = $userDisabledCheckCommand;
- $this->uidLoginCommand = $uidLoginCommand;
- $this->emailLoginCommand = $emailLoginCommand;
- $this->loggedInCheckCommand = $loggedInCheckCommand;
- $this->completeLoginCommand = $completeLoginCommand;
- $this->createSessionTokenCommand = $createSessionTokenCommand;
- $this->clearLostPasswordTokensCommand = $clearLostPasswordTokensCommand;
- $this->updateLastPasswordConfirmCommand = $updateLastPasswordConfirmCommand;
- $this->setUserTimezoneCommand = $setUserTimezoneCommand;
- $this->twoFactorCommand = $twoFactorCommand;
- $this->finishRememberedLoginCommand = $finishRememberedLoginCommand;
}
public function process(LoginData $loginData): LoginResult {
@@ -77,9 +30,9 @@ class Chain {
$chain
->setNext($this->userDisabledCheckCommand)
->setNext($this->uidLoginCommand)
- ->setNext($this->emailLoginCommand)
->setNext($this->loggedInCheckCommand)
->setNext($this->completeLoginCommand)
+ ->setNext($this->flowV2EphemeralSessionsCommand)
->setNext($this->createSessionTokenCommand)
->setNext($this->clearLostPasswordTokensCommand)
->setNext($this->updateLastPasswordConfirmCommand)
diff --git a/lib/private/Authentication/Login/EmailLoginCommand.php b/lib/private/Authentication/Login/EmailLoginCommand.php
deleted file mode 100644
index 96cb39277fd..00000000000
--- a/lib/private/Authentication/Login/EmailLoginCommand.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-namespace OC\Authentication\Login;
-
-use OCP\IUserManager;
-
-class EmailLoginCommand extends ALoginCommand {
- /** @var IUserManager */
- private $userManager;
-
- public function __construct(IUserManager $userManager) {
- $this->userManager = $userManager;
- }
-
- public function process(LoginData $loginData): LoginResult {
- if ($loginData->getUser() === false) {
- if (!filter_var($loginData->getUsername(), FILTER_VALIDATE_EMAIL)) {
- return $this->processNextOrFinishSuccessfully($loginData);
- }
-
- $users = $this->userManager->getByEmail($loginData->getUsername());
- // we only allow login by email if unique
- if (count($users) === 1) {
- // FIXME: This is a workaround to still stick to configured LDAP login filters
- // this can be removed once the email login is properly implemented in the local user backend
- // as described in https://github.com/nextcloud/server/issues/5221
- if ($users[0]->getBackendClassName() === 'LDAP') {
- return $this->processNextOrFinishSuccessfully($loginData);
- }
-
- $username = $users[0]->getUID();
- if ($username !== $loginData->getUsername()) {
- $user = $this->userManager->checkPassword(
- $username,
- $loginData->getPassword()
- );
- if ($user !== false) {
- $loginData->setUser($user);
- $loginData->setUsername($username);
- }
- }
- }
- }
-
- return $this->processNextOrFinishSuccessfully($loginData);
- }
-}
diff --git a/lib/private/Authentication/Login/FlowV2EphemeralSessionsCommand.php b/lib/private/Authentication/Login/FlowV2EphemeralSessionsCommand.php
new file mode 100644
index 00000000000..82dd829334d
--- /dev/null
+++ b/lib/private/Authentication/Login/FlowV2EphemeralSessionsCommand.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OC\Authentication\Login;
+
+use OC\Core\Controller\ClientFlowLoginV2Controller;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+class FlowV2EphemeralSessionsCommand extends ALoginCommand {
+ public function __construct(
+ private ISession $session,
+ private IURLGenerator $urlGenerator,
+ ) {
+ }
+
+ public function process(LoginData $loginData): LoginResult {
+ $loginV2GrantRoute = $this->urlGenerator->linkToRoute('core.ClientFlowLoginV2.grantPage');
+ if (str_starts_with($loginData->getRedirectUrl() ?? '', $loginV2GrantRoute)) {
+ $this->session->set(ClientFlowLoginV2Controller::EPHEMERAL_NAME, true);
+ }
+
+ return $this->processNextOrFinishSuccessfully($loginData);
+ }
+}
diff --git a/lib/private/Authentication/Login/WebAuthnChain.php b/lib/private/Authentication/Login/WebAuthnChain.php
index c31e39de28c..ae523c43da6 100644
--- a/lib/private/Authentication/Login/WebAuthnChain.php
+++ b/lib/private/Authentication/Login/WebAuthnChain.php
@@ -48,7 +48,7 @@ class WebAuthnChain {
UpdateLastPasswordConfirmCommand $updateLastPasswordConfirmCommand,
SetUserTimezoneCommand $setUserTimezoneCommand,
TwoFactorCommand $twoFactorCommand,
- FinishRememberedLoginCommand $finishRememberedLoginCommand
+ FinishRememberedLoginCommand $finishRememberedLoginCommand,
) {
$this->userDisabledCheckCommand = $userDisabledCheckCommand;
$this->webAuthnLoginCommand = $webAuthnLoginCommand;
diff --git a/lib/private/Authentication/LoginCredentials/Credentials.php b/lib/private/Authentication/LoginCredentials/Credentials.php
index 2d7ed3adfd0..3414034b33c 100644
--- a/lib/private/Authentication/LoginCredentials/Credentials.php
+++ b/lib/private/Authentication/LoginCredentials/Credentials.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/lib/private/Authentication/LoginCredentials/Store.php b/lib/private/Authentication/LoginCredentials/Store.php
index bd39dd11460..67c5712715c 100644
--- a/lib/private/Authentication/LoginCredentials/Store.php
+++ b/lib/private/Authentication/LoginCredentials/Store.php
@@ -8,6 +8,7 @@ declare(strict_types=1);
*/
namespace OC\Authentication\LoginCredentials;
+use Exception;
use OC\Authentication\Exceptions\PasswordlessTokenException;
use OC\Authentication\Token\IProvider;
use OCP\Authentication\Exceptions\CredentialsUnavailableException;
@@ -15,6 +16,7 @@ use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\LoginCredentials\ICredentials;
use OCP\Authentication\LoginCredentials\IStore;
use OCP\ISession;
+use OCP\Security\ICrypto;
use OCP\Session\Exceptions\SessionNotAvailableException;
use OCP\Util;
use Psr\Log\LoggerInterface;
@@ -29,9 +31,12 @@ class Store implements IStore {
/** @var IProvider|null */
private $tokenProvider;
- public function __construct(ISession $session,
+ public function __construct(
+ ISession $session,
LoggerInterface $logger,
- ?IProvider $tokenProvider = null) {
+ private readonly ICrypto $crypto,
+ ?IProvider $tokenProvider = null,
+ ) {
$this->session = $session;
$this->logger = $logger;
$this->tokenProvider = $tokenProvider;
@@ -45,6 +50,9 @@ class Store implements IStore {
* @param array $params
*/
public function authenticate(array $params) {
+ if ($params['password'] !== null) {
+ $params['password'] = $this->crypto->encrypt((string)$params['password']);
+ }
$this->session->set('login_credentials', json_encode($params));
}
@@ -91,6 +99,13 @@ class Store implements IStore {
if ($trySession && $this->session->exists('login_credentials')) {
/** @var array $creds */
$creds = json_decode($this->session->get('login_credentials'), true);
+ if ($creds['password'] !== null) {
+ try {
+ $creds['password'] = $this->crypto->decrypt($creds['password']);
+ } catch (Exception $e) {
+ //decryption failed, continue with old password as it is
+ }
+ }
return new Credentials(
$creds['uid'],
$creds['loginName'] ?? $this->session->get('loginname') ?? $creds['uid'], // Pre 20 didn't have a loginName property, hence fall back to the session value and then to the UID
diff --git a/lib/private/Authentication/Notifications/Notifier.php b/lib/private/Authentication/Notifications/Notifier.php
index 3b6c9b3e610..a81e385d8b1 100644
--- a/lib/private/Authentication/Notifications/Notifier.php
+++ b/lib/private/Authentication/Notifications/Notifier.php
@@ -8,10 +8,10 @@ declare(strict_types=1);
*/
namespace OC\Authentication\Notifications;
-use InvalidArgumentException;
use OCP\L10N\IFactory as IL10nFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
+use OCP\Notification\UnknownNotificationException;
class Notifier implements INotifier {
/** @var IL10nFactory */
@@ -27,7 +27,7 @@ class Notifier implements INotifier {
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== 'auth') {
// Not my app => throw
- throw new InvalidArgumentException();
+ throw new UnknownNotificationException();
}
// Read the language from the notification
@@ -52,7 +52,7 @@ class Notifier implements INotifier {
return $notification;
default:
// Unknown subject => Unknown notification => throw
- throw new InvalidArgumentException();
+ throw new UnknownNotificationException();
}
}
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index dfb17301ab3..d47427e79bf 100644
--- a/lib/private/Authentication/Token/IProvider.php
+++ b/lib/private/Authentication/Token/IProvider.php
@@ -35,7 +35,9 @@ interface IProvider {
?string $password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
- int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken;
+ int $remember = OCPIToken::DO_NOT_REMEMBER,
+ ?array $scope = null,
+ ): OCPIToken;
/**
* Get a token by token id
diff --git a/lib/private/Authentication/Token/Manager.php b/lib/private/Authentication/Token/Manager.php
index 37ed6083d82..6953f47b004 100644
--- a/lib/private/Authentication/Token/Manager.php
+++ b/lib/private/Authentication/Token/Manager.php
@@ -42,7 +42,9 @@ class Manager implements IProvider, OCPIProvider {
$password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
- int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
+ int $remember = OCPIToken::DO_NOT_REMEMBER,
+ ?array $scope = null,
+ ): OCPIToken {
if (mb_strlen($name) > 128) {
$name = mb_substr($name, 0, 120) . '…';
}
@@ -55,7 +57,8 @@ class Manager implements IProvider, OCPIProvider {
$password,
$name,
$type,
- $remember
+ $remember,
+ $scope,
);
} catch (UniqueConstraintViolationException $e) {
// It's rare, but if two requests of the same session (e.g. env-based SAML)
diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php
index 961b7191d84..be427ab4839 100644
--- a/lib/private/Authentication/Token/PublicKeyToken.php
+++ b/lib/private/Authentication/Token/PublicKeyToken.php
@@ -10,6 +10,7 @@ namespace OC\Authentication\Token;
use OCP\AppFramework\Db\Entity;
use OCP\Authentication\Token\IToken;
+use OCP\DB\Types;
/**
* @method void setId(int $id)
@@ -88,16 +89,16 @@ class PublicKeyToken extends Entity implements INamedToken, IWipeableToken {
$this->addType('passwordHash', 'string');
$this->addType('name', 'string');
$this->addType('token', 'string');
- $this->addType('type', 'int');
- $this->addType('remember', 'int');
- $this->addType('lastActivity', 'int');
- $this->addType('lastCheck', 'int');
+ $this->addType('type', Types::INTEGER);
+ $this->addType('remember', Types::INTEGER);
+ $this->addType('lastActivity', Types::INTEGER);
+ $this->addType('lastCheck', Types::INTEGER);
$this->addType('scope', 'string');
- $this->addType('expires', 'int');
+ $this->addType('expires', Types::INTEGER);
$this->addType('publicKey', 'string');
$this->addType('privateKey', 'string');
- $this->addType('version', 'int');
- $this->addType('passwordInvalid', 'bool');
+ $this->addType('version', Types::INTEGER);
+ $this->addType('passwordInvalid', Types::BOOLEAN);
}
public function getId(): int {
diff --git a/lib/private/Authentication/Token/PublicKeyTokenMapper.php b/lib/private/Authentication/Token/PublicKeyTokenMapper.php
index 0db5c4f53e7..9aabd69e57a 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenMapper.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenMapper.php
@@ -31,22 +31,25 @@ class PublicKeyTokenMapper extends QBMapper {
$qb->delete($this->tableName)
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
+ ->executeStatement();
}
/**
* @param int $olderThan
- * @param int $remember
+ * @param int $type
+ * @param int|null $remember
*/
- public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) {
+ public function invalidateOld(int $olderThan, int $type = IToken::TEMPORARY_TOKEN, ?int $remember = null) {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
- $qb->delete($this->tableName)
+ $delete = $qb->delete($this->tableName)
->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)))
- ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
+ ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
+ if ($remember !== null) {
+ $delete->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)));
+ }
+ $delete->executeStatement();
}
public function invalidateLastUsedBefore(string $uid, int $before): int {
@@ -70,7 +73,7 @@ class PublicKeyTokenMapper extends QBMapper {
->from($this->tableName)
->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
+ ->executeQuery();
$data = $result->fetch();
$result->closeCursor();
@@ -92,7 +95,7 @@ class PublicKeyTokenMapper extends QBMapper {
->from($this->tableName)
->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
- ->execute();
+ ->executeQuery();
$data = $result->fetch();
$result->closeCursor();
@@ -119,7 +122,7 @@ class PublicKeyTokenMapper extends QBMapper {
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
->setMaxResults(1000);
- $result = $qb->execute();
+ $result = $qb->executeQuery();
$data = $result->fetchAll();
$result->closeCursor();
@@ -151,7 +154,7 @@ class PublicKeyTokenMapper extends QBMapper {
$qb->delete($this->tableName)
->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
- $qb->execute();
+ $qb->executeStatement();
}
public function deleteTempToken(PublicKeyToken $except) {
@@ -163,7 +166,7 @@ class PublicKeyTokenMapper extends QBMapper {
->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($except->getId())))
->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
- $qb->execute();
+ $qb->executeStatement();
}
public function hasExpiredTokens(string $uid): bool {
@@ -174,7 +177,7 @@ class PublicKeyTokenMapper extends QBMapper {
->andWhere($qb->expr()->eq('password_invalid', $qb->createNamedParameter(true), IQueryBuilder::PARAM_BOOL))
->setMaxResults(1);
- $cursor = $qb->execute();
+ $cursor = $qb->executeQuery();
$data = $cursor->fetchAll();
$cursor->closeCursor();
diff --git a/lib/private/Authentication/Token/PublicKeyTokenProvider.php b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
index 767ece1e551..12c3a1d535b 100644
--- a/lib/private/Authentication/Token/PublicKeyTokenProvider.php
+++ b/lib/private/Authentication/Token/PublicKeyTokenProvider.php
@@ -85,7 +85,9 @@ class PublicKeyTokenProvider implements IProvider {
?string $password,
string $name,
int $type = OCPIToken::TEMPORARY_TOKEN,
- int $remember = OCPIToken::DO_NOT_REMEMBER): OCPIToken {
+ int $remember = OCPIToken::DO_NOT_REMEMBER,
+ ?array $scope = null,
+ ): OCPIToken {
if (strlen($token) < self::TOKEN_MIN_LENGTH) {
$exception = new InvalidTokenException('Token is too short, minimum of ' . self::TOKEN_MIN_LENGTH . ' characters is required, ' . strlen($token) . ' characters given');
$this->logger->error('Invalid token provided when generating new token', ['exception' => $exception]);
@@ -107,6 +109,10 @@ class PublicKeyTokenProvider implements IProvider {
$dbToken->setPasswordHash($randomOldToken->getPasswordHash());
}
+ if ($scope !== null) {
+ $dbToken->setScope($scope);
+ }
+
$this->mapper->insert($dbToken);
if (!$oldTokenMatches && $password !== null) {
@@ -156,7 +162,7 @@ class PublicKeyTokenProvider implements IProvider {
$this->rotate($token, $tokenId, $tokenId);
} catch (DoesNotExistException) {
$this->cacheInvalidHash($tokenHash);
- throw new InvalidTokenException("Token does not exist: " . $ex->getMessage(), 0, $ex);
+ throw new InvalidTokenException('Token does not exist: ' . $ex->getMessage(), 0, $ex);
}
}
@@ -171,7 +177,7 @@ class PublicKeyTokenProvider implements IProvider {
private function getTokenFromCache(string $tokenHash): ?PublicKeyToken {
$serializedToken = $this->cache->get($tokenHash);
if ($serializedToken === false) {
- throw new InvalidTokenException('Token does not exist: ' . $tokenHash);
+ return null;
}
if ($serializedToken === null) {
@@ -226,7 +232,7 @@ class PublicKeyTokenProvider implements IProvider {
$token = $this->getToken($oldSessionId);
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
$password = null;
@@ -234,6 +240,8 @@ class PublicKeyTokenProvider implements IProvider {
$privateKey = $this->decrypt($token->getPrivateKey(), $oldSessionId);
$password = $this->decryptPassword($token->getPassword(), $privateKey);
}
+
+ $scope = $token->getScope() === '' ? null : $token->getScopeAsArray();
$newToken = $this->generateToken(
$sessionId,
$token->getUID(),
@@ -241,9 +249,9 @@ class PublicKeyTokenProvider implements IProvider {
$password,
$token->getName(),
OCPIToken::TEMPORARY_TOKEN,
- $token->getRemember()
+ $token->getRemember(),
+ $scope,
);
- $newToken->setScope($token->getScopeAsArray());
$this->cacheToken($newToken);
$this->cacheInvalidHash($token->getToken());
@@ -273,10 +281,19 @@ class PublicKeyTokenProvider implements IProvider {
public function invalidateOldTokens() {
$olderThan = $this->time->getTime() - $this->config->getSystemValueInt('session_lifetime', 60 * 60 * 24);
$this->logger->debug('Invalidating session tokens older than ' . date('c', $olderThan), ['app' => 'cron']);
- $this->mapper->invalidateOld($olderThan, OCPIToken::DO_NOT_REMEMBER);
+ $this->mapper->invalidateOld($olderThan, OCPIToken::TEMPORARY_TOKEN, OCPIToken::DO_NOT_REMEMBER);
+
$rememberThreshold = $this->time->getTime() - $this->config->getSystemValueInt('remember_login_cookie_lifetime', 60 * 60 * 24 * 15);
$this->logger->debug('Invalidating remembered session tokens older than ' . date('c', $rememberThreshold), ['app' => 'cron']);
- $this->mapper->invalidateOld($rememberThreshold, OCPIToken::REMEMBER);
+ $this->mapper->invalidateOld($rememberThreshold, OCPIToken::TEMPORARY_TOKEN, OCPIToken::REMEMBER);
+
+ $wipeThreshold = $this->time->getTime() - $this->config->getSystemValueInt('token_auth_wipe_token_retention', 60 * 60 * 24 * 60);
+ $this->logger->debug('Invalidating auth tokens marked for remote wipe older than ' . date('c', $wipeThreshold), ['app' => 'cron']);
+ $this->mapper->invalidateOld($wipeThreshold, OCPIToken::WIPE_TOKEN);
+
+ $authTokenThreshold = $this->time->getTime() - $this->config->getSystemValueInt('token_auth_token_retention', 60 * 60 * 24 * 365);
+ $this->logger->debug('Invalidating auth tokens older than ' . date('c', $authTokenThreshold), ['app' => 'cron']);
+ $this->mapper->invalidateOld($authTokenThreshold, OCPIToken::PERMANENT_TOKEN);
}
public function invalidateLastUsedBefore(string $uid, int $before): void {
@@ -285,7 +302,7 @@ class PublicKeyTokenProvider implements IProvider {
public function updateToken(OCPIToken $token) {
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
$this->mapper->update($token);
$this->cacheToken($token);
@@ -293,7 +310,7 @@ class PublicKeyTokenProvider implements IProvider {
public function updateTokenActivity(OCPIToken $token) {
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
$activityInterval = $this->config->getSystemValueInt('token_auth_activity_update', 60);
@@ -314,7 +331,7 @@ class PublicKeyTokenProvider implements IProvider {
public function getPassword(OCPIToken $savedToken, string $tokenId): string {
if (!($savedToken instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
if ($savedToken->getPassword() === null) {
@@ -330,7 +347,7 @@ class PublicKeyTokenProvider implements IProvider {
public function setPassword(OCPIToken $token, string $tokenId, string $password) {
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
$this->atomic(function () use ($password, $token) {
@@ -355,7 +372,7 @@ class PublicKeyTokenProvider implements IProvider {
public function rotate(OCPIToken $token, string $oldTokenId, string $newTokenId): OCPIToken {
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
// Decrypt private key with oldTokenId
@@ -388,7 +405,7 @@ class PublicKeyTokenProvider implements IProvider {
} catch (\Exception $ex2) {
// Delete the invalid token
$this->invalidateToken($token);
- throw new InvalidTokenException("Could not decrypt token password: " . $ex->getMessage(), 0, $ex2);
+ throw new InvalidTokenException('Could not decrypt token password: ' . $ex->getMessage(), 0, $ex2);
}
}
}
@@ -413,7 +430,7 @@ class PublicKeyTokenProvider implements IProvider {
}
/**
- * @deprecated Fallback for instances where the secret might not have been set by accident
+ * @deprecated 26.0.0 Fallback for instances where the secret might not have been set by accident
*/
private function hashTokenWithEmptySecret(string $token): string {
return hash('sha512', $token);
@@ -478,7 +495,7 @@ class PublicKeyTokenProvider implements IProvider {
public function markPasswordInvalid(OCPIToken $token, string $tokenId) {
if (!($token instanceof PublicKeyToken)) {
- throw new InvalidTokenException("Invalid token type");
+ throw new InvalidTokenException('Invalid token type');
}
$token->setPasswordInvalid(true);
diff --git a/lib/private/Authentication/Token/RemoteWipe.php b/lib/private/Authentication/Token/RemoteWipe.php
index 43c2bd060d1..80ba330b66d 100644
--- a/lib/private/Authentication/Token/RemoteWipe.php
+++ b/lib/private/Authentication/Token/RemoteWipe.php
@@ -98,7 +98,7 @@ class RemoteWipe {
$dbToken = $e->getToken();
- $this->logger->info("user " . $dbToken->getUID() . " started a remote wipe");
+ $this->logger->info('user ' . $dbToken->getUID() . ' started a remote wipe');
$this->eventDispatcher->dispatch(RemoteWipeStarted::class, new RemoteWipeStarted($dbToken));
@@ -126,7 +126,7 @@ class RemoteWipe {
$this->tokenProvider->invalidateToken($token);
- $this->logger->info("user " . $dbToken->getUID() . " finished a remote wipe");
+ $this->logger->info('user ' . $dbToken->getUID() . ' finished a remote wipe');
$this->eventDispatcher->dispatch(RemoteWipeFinished::class, new RemoteWipeFinished($dbToken));
return true;
diff --git a/lib/private/Authentication/Token/TokenCleanupJob.php b/lib/private/Authentication/Token/TokenCleanupJob.php
index 041d2e8a5e2..e6d1e69e9b4 100644
--- a/lib/private/Authentication/Token/TokenCleanupJob.php
+++ b/lib/private/Authentication/Token/TokenCleanupJob.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-only
diff --git a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
index c84b7f1af20..cc468dbeba0 100644
--- a/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
+++ b/lib/private/Authentication/TwoFactorAuth/Db/ProviderUserAssignmentDao.php
@@ -29,7 +29,7 @@ class ProviderUserAssignmentDao {
* Get all assigned provider IDs for the given user ID
*
* @return array<string, bool> where the array key is the provider ID (string) and the
- * value is the enabled state (bool)
+ * value is the enabled state (bool)
*/
public function getState(string $uid): array {
$qb = $this->conn->getQueryBuilder();
@@ -37,7 +37,7 @@ class ProviderUserAssignmentDao {
$query = $qb->select('provider_id', 'enabled')
->from(self::TABLE_NAME)
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
- $result = $query->execute();
+ $result = $query->executeQuery();
$providers = [];
foreach ($result->fetchAll() as $row) {
$providers[(string)$row['provider_id']] = (int)$row['enabled'] === 1;
@@ -95,7 +95,7 @@ class ProviderUserAssignmentDao {
return [
'provider_id' => (string)$row['provider_id'],
'uid' => (string)$row['uid'],
- 'enabled' => ((int) $row['enabled']) === 1,
+ 'enabled' => ((int)$row['enabled']) === 1,
];
}, $rows));
}
@@ -106,6 +106,6 @@ class ProviderUserAssignmentDao {
$deleteQuery = $qb->delete(self::TABLE_NAME)
->where($qb->expr()->eq('provider_id', $qb->createNamedParameter($providerId)));
- $deleteQuery->execute();
+ $deleteQuery->executeStatement();
}
}
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index 2585646c998..07aa98610ed 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -12,6 +12,7 @@ use BadMethodCallException;
use Exception;
use OC\Authentication\Token\IProvider as TokenProvider;
use OCP\Activity\IManager;
+use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Authentication\Exceptions\InvalidTokenException;
use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
@@ -192,7 +193,7 @@ class Manager {
if (!empty($missing)) {
// There was at least one provider missing
- $this->logger->alert(count($missing) . " two-factor auth providers failed to load", ['app' => 'core']);
+ $this->logger->alert(count($missing) . ' two-factor auth providers failed to load', ['app' => 'core']);
return true;
}
@@ -307,8 +308,8 @@ class Manager {
// First check if the session tells us we should do 2FA (99% case)
if (!$this->session->exists(self::SESSION_UID_KEY)) {
// Check if the session tells us it is 2FA authenticated already
- if ($this->session->exists(self::SESSION_UID_DONE) &&
- $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
+ if ($this->session->exists(self::SESSION_UID_DONE)
+ && $this->session->get(self::SESSION_UID_DONE) === $user->getUID()) {
return false;
}
@@ -322,7 +323,7 @@ class Manager {
$tokenId = $token->getId();
$tokensNeeding2FA = $this->config->getUserKeys($user->getUID(), 'login_token_2fa');
- if (!\in_array((string) $tokenId, $tokensNeeding2FA, true)) {
+ if (!\in_array((string)$tokenId, $tokensNeeding2FA, true)) {
$this->session->set(self::SESSION_UID_DONE, $user->getUID());
return false;
}
@@ -359,14 +360,19 @@ class Manager {
$id = $this->session->getId();
$token = $this->tokenProvider->getToken($id);
- $this->config->setUserValue($user->getUID(), 'login_token_2fa', (string) $token->getId(), (string)$this->timeFactory->getTime());
+ $this->config->setUserValue($user->getUID(), 'login_token_2fa', (string)$token->getId(), (string)$this->timeFactory->getTime());
}
public function clearTwoFactorPending(string $userId) {
$tokensNeeding2FA = $this->config->getUserKeys($userId, 'login_token_2fa');
foreach ($tokensNeeding2FA as $tokenId) {
- $this->tokenProvider->invalidateTokenById($userId, (int)$tokenId);
+ $this->config->deleteUserValue($userId, 'login_token_2fa', $tokenId);
+
+ try {
+ $this->tokenProvider->invalidateTokenById($userId, (int)$tokenId);
+ } catch (DoesNotExistException $e) {
+ }
}
}
}
diff --git a/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php b/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php
index b9a0a97bec4..7e674a01dd8 100644
--- a/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php
+++ b/lib/private/Authentication/TwoFactorAuth/ProviderLoader.php
@@ -9,8 +9,7 @@ declare(strict_types=1);
namespace OC\Authentication\TwoFactorAuth;
use Exception;
-use OC;
-use OC_App;
+use OC\AppFramework\Bootstrap\Coordinator;
use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;
use OCP\Authentication\TwoFactorAuth\IProvider;
@@ -19,15 +18,10 @@ use OCP\IUser;
class ProviderLoader {
public const BACKUP_CODES_APP_ID = 'twofactor_backupcodes';
- /** @var IAppManager */
- private $appManager;
-
- /** @var OC\AppFramework\Bootstrap\Coordinator */
- private $coordinator;
-
- public function __construct(IAppManager $appManager, OC\AppFramework\Bootstrap\Coordinator $coordinator) {
- $this->appManager = $appManager;
- $this->coordinator = $coordinator;
+ public function __construct(
+ private IAppManager $appManager,
+ private Coordinator $coordinator,
+ ) {
}
/**
@@ -58,12 +52,12 @@ class ProviderLoader {
}
}
- $registeredProviders = $this->coordinator->getRegistrationContext()->getTwoFactorProviders();
+ $registeredProviders = $this->coordinator->getRegistrationContext()?->getTwoFactorProviders() ?? [];
foreach ($registeredProviders as $provider) {
try {
$this->loadTwoFactorApp($provider->getAppId());
- $provider = \OCP\Server::get($provider->getService());
- $providers[$provider->getId()] = $provider;
+ $providerInstance = \OCP\Server::get($provider->getService());
+ $providers[$providerInstance->getId()] = $providerInstance;
} catch (QueryException $exc) {
// Provider class can not be resolved
throw new Exception('Could not load two-factor auth provider ' . $provider->getService());
@@ -75,12 +69,10 @@ class ProviderLoader {
/**
* Load an app by ID if it has not been loaded yet
- *
- * @param string $appId
*/
- protected function loadTwoFactorApp(string $appId) {
- if (!OC_App::isAppLoaded($appId)) {
- OC_App::loadApp($appId);
+ protected function loadTwoFactorApp(string $appId): void {
+ if (!$this->appManager->isAppLoaded($appId)) {
+ $this->appManager->loadApp($appId);
}
}
}
diff --git a/lib/private/Authentication/WebAuthn/CredentialRepository.php b/lib/private/Authentication/WebAuthn/CredentialRepository.php
index f32136f9594..203f2ef9020 100644
--- a/lib/private/Authentication/WebAuthn/CredentialRepository.php
+++ b/lib/private/Authentication/WebAuthn/CredentialRepository.php
@@ -44,7 +44,7 @@ class CredentialRepository implements PublicKeyCredentialSourceRepository {
}, $entities);
}
- public function saveAndReturnCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, ?string $name = null): PublicKeyCredentialEntity {
+ public function saveAndReturnCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, ?string $name = null, bool $userVerification = false): PublicKeyCredentialEntity {
$oldEntity = null;
try {
@@ -58,13 +58,18 @@ class CredentialRepository implements PublicKeyCredentialSourceRepository {
$name = 'default';
}
- $entity = PublicKeyCredentialEntity::fromPublicKeyCrendentialSource($name, $publicKeyCredentialSource);
+ $entity = PublicKeyCredentialEntity::fromPublicKeyCrendentialSource($name, $publicKeyCredentialSource, $userVerification);
if ($oldEntity) {
$entity->setId($oldEntity->getId());
if ($defaultName) {
$entity->setName($oldEntity->getName());
}
+
+ // Don't downgrade UV just because it was skipped during a login due to another key
+ if ($oldEntity->getUserVerification()) {
+ $entity->setUserVerification(true);
+ }
}
return $this->credentialMapper->insertOrUpdate($entity);
diff --git a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
index 443a7985cae..6c4bc3ca81b 100644
--- a/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
+++ b/lib/private/Authentication/WebAuthn/Db/PublicKeyCredentialEntity.php
@@ -23,6 +23,10 @@ use Webauthn\PublicKeyCredentialSource;
* @method void setPublicKeyCredentialId(string $id);
* @method string getData();
* @method void setData(string $data);
+ *
+ * @since 30.0.0 Add userVerification attribute
+ * @method bool|null getUserVerification();
+ * @method void setUserVerification(bool $userVerification);
*/
class PublicKeyCredentialEntity extends Entity implements JsonSerializable {
/** @var string */
@@ -37,20 +41,25 @@ class PublicKeyCredentialEntity extends Entity implements JsonSerializable {
/** @var string */
protected $data;
+ /** @var bool|null */
+ protected $userVerification;
+
public function __construct() {
$this->addType('name', 'string');
$this->addType('uid', 'string');
$this->addType('publicKeyCredentialId', 'string');
$this->addType('data', 'string');
+ $this->addType('userVerification', 'boolean');
}
- public static function fromPublicKeyCrendentialSource(string $name, PublicKeyCredentialSource $publicKeyCredentialSource): PublicKeyCredentialEntity {
+ public static function fromPublicKeyCrendentialSource(string $name, PublicKeyCredentialSource $publicKeyCredentialSource, bool $userVerification): PublicKeyCredentialEntity {
$publicKeyCredentialEntity = new self();
$publicKeyCredentialEntity->setName($name);
$publicKeyCredentialEntity->setUid($publicKeyCredentialSource->getUserHandle());
$publicKeyCredentialEntity->setPublicKeyCredentialId(base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()));
$publicKeyCredentialEntity->setData(json_encode($publicKeyCredentialSource));
+ $publicKeyCredentialEntity->setUserVerification($userVerification);
return $publicKeyCredentialEntity;
}
diff --git a/lib/private/Authentication/WebAuthn/Manager.php b/lib/private/Authentication/WebAuthn/Manager.php
index 007be245992..96dc0719b54 100644
--- a/lib/private/Authentication/WebAuthn/Manager.php
+++ b/lib/private/Authentication/WebAuthn/Manager.php
@@ -53,7 +53,7 @@ class Manager {
CredentialRepository $repository,
PublicKeyCredentialMapper $credentialMapper,
LoggerInterface $logger,
- IConfig $config
+ IConfig $config,
) {
$this->repository = $repository;
$this->credentialMapper = $credentialMapper;
@@ -88,8 +88,8 @@ class Manager {
];
$authenticatorSelectionCriteria = new AuthenticatorSelectionCriteria(
- null,
- AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
+ AuthenticatorSelectionCriteria::AUTHENTICATOR_ATTACHMENT_NO_PREFERENCE,
+ AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_PREFERRED,
null,
false,
);
@@ -151,7 +151,8 @@ class Manager {
}
// Persist the data
- return $this->repository->saveAndReturnCredentialSource($publicKeyCredentialSource, $name);
+ $userVerification = $response->attestationObject->authData->isUserVerified();
+ return $this->repository->saveAndReturnCredentialSource($publicKeyCredentialSource, $name, $userVerification);
}
private function stripPort(string $serverHost): string {
@@ -160,7 +161,11 @@ class Manager {
public function startAuthentication(string $uid, string $serverHost): PublicKeyCredentialRequestOptions {
// List of registered PublicKeyCredentialDescriptor classes associated to the user
- $registeredPublicKeyCredentialDescriptors = array_map(function (PublicKeyCredentialEntity $entity) {
+ $userVerificationRequirement = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED;
+ $registeredPublicKeyCredentialDescriptors = array_map(function (PublicKeyCredentialEntity $entity) use (&$userVerificationRequirement) {
+ if ($entity->getUserVerification() !== true) {
+ $userVerificationRequirement = AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED;
+ }
$credential = $entity->toPublicKeyCredentialSource();
return new PublicKeyCredentialDescriptor(
$credential->type,
@@ -173,7 +178,7 @@ class Manager {
random_bytes(32), // Challenge
$this->stripPort($serverHost), // Relying Party ID
$registeredPublicKeyCredentialDescriptors, // Registered PublicKeyCredentialDescriptor classes
- AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_DISCOURAGED,
+ $userVerificationRequirement,
60000, // Timeout
);
}
@@ -241,14 +246,6 @@ class Manager {
}
public function isWebAuthnAvailable(): bool {
- if (!extension_loaded('bcmath')) {
- return false;
- }
-
- if (!extension_loaded('gmp')) {
- return false;
- }
-
if (!$this->config->getSystemValueBool('auth.webauthn.enabled', true)) {
return false;
}