diff options
Diffstat (limited to 'apps/files_external/lib/Lib/Auth/Password')
6 files changed, 439 insertions, 0 deletions
diff --git a/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php new file mode 100644 index 00000000000..916b496b506 --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/GlobalAuth.php @@ -0,0 +1,80 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\Service\BackendService; +use OCP\IL10N; +use OCP\IUser; +use OCP\Security\ICredentialsManager; + +/** + * Global Username and Password + */ +class GlobalAuth extends AuthMechanism { + public const CREDENTIALS_IDENTIFIER = 'password::global'; + private const PWD_PLACEHOLDER = '************************'; + + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { + $this + ->setIdentifier('password::global') + ->setVisibility(BackendService::VISIBILITY_DEFAULT) + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Global credentials')); + } + + public function getAuth($uid) { + $auth = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + if (!is_array($auth)) { + return [ + 'user' => '', + 'password' => '' + ]; + } else { + $auth['password'] = self::PWD_PLACEHOLDER; + return $auth; + } + } + + public function saveAuth($uid, $user, $password) { + // Use old password if it has not changed. + if ($password === self::PWD_PLACEHOLDER) { + $auth = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + $password = $auth['password']; + } + + $this->credentialsManager->store($uid, self::CREDENTIALS_IDENTIFIER, [ + 'user' => $user, + 'password' => $password + ]); + } + + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { + if ($storage->getType() === StorageConfig::MOUNT_TYPE_ADMIN) { + $uid = ''; + } elseif (is_null($user)) { + throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); + } else { + $uid = $user->getUID(); + } + $credentials = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + + if (is_array($credentials)) { + $storage->setBackendOption('user', $credentials['user']); + $storage->setBackendOption('password', $credentials['password']); + } + } +} diff --git a/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php new file mode 100644 index 00000000000..ce38140b6ee --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php @@ -0,0 +1,113 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\Listener\StorePasswordListener; +use OCP\Authentication\Exceptions\CredentialsUnavailableException; +use OCP\Authentication\LoginCredentials\IStore as CredentialsStore; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IL10N; +use OCP\ISession; +use OCP\IUser; +use OCP\IUserBackend; +use OCP\LDAP\ILDAPProviderFactory; +use OCP\Security\ICredentialsManager; +use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserLoggedInEvent; + +/** + * Username and password from login credentials, saved in DB + */ +class LoginCredentials extends AuthMechanism { + public const CREDENTIALS_IDENTIFIER = 'password::logincredentials/credentials'; + + public function __construct( + IL10N $l, + protected ISession $session, + protected ICredentialsManager $credentialsManager, + private CredentialsStore $credentialsStore, + IEventDispatcher $eventDispatcher, + private ILDAPProviderFactory $ldapFactory, + ) { + $this + ->setIdentifier('password::logincredentials') + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Log-in credentials, save in database')) + ->addParameters([ + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + ]); + + $eventDispatcher->addServiceListener(UserLoggedInEvent::class, StorePasswordListener::class); + $eventDispatcher->addServiceListener(PasswordUpdatedEvent::class, StorePasswordListener::class); + } + + private function getCredentials(IUser $user): array { + $credentials = $this->credentialsManager->retrieve($user->getUID(), self::CREDENTIALS_IDENTIFIER); + + if (is_null($credentials)) { + // nothing saved in db, try to get it from the session and save it + try { + $sessionCredentials = $this->credentialsStore->getLoginCredentials(); + + if ($sessionCredentials->getUID() !== $user->getUID()) { + // Can't take the credentials from the session as they are not the same user + throw new CredentialsUnavailableException(); + } + + $credentials = [ + 'user' => $sessionCredentials->getLoginName(), + 'password' => $sessionCredentials->getPassword(), + ]; + + $this->credentialsManager->store($user->getUID(), self::CREDENTIALS_IDENTIFIER, $credentials); + } catch (CredentialsUnavailableException $e) { + throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved'); + } + } + + return $credentials; + } + + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { + if (!isset($user)) { + throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved'); + } + $credentials = $this->getCredentials($user); + + $loginKey = $storage->getBackendOption('login_ldap_attr'); + if ($loginKey) { + $backend = $user->getBackend(); + if ($backend instanceof IUserBackend && $backend->getBackendName() === 'LDAP') { + $value = $this->getLdapPropertyForUser($user, $loginKey); + if ($value === null) { + throw new InsufficientDataForMeaningfulAnswerException('Custom ldap attribute not set for user ' . $user->getUID()); + } + $storage->setBackendOption('user', $value); + } else { + throw new InsufficientDataForMeaningfulAnswerException('Custom ldap attribute configured but user ' . $user->getUID() . ' is not an ldap user'); + } + } else { + $storage->setBackendOption('user', $credentials['user']); + } + $storage->setBackendOption('password', $credentials['password']); + } + + private function getLdapPropertyForUser(IUser $user, string $property): ?string { + return $this->ldapFactory->getLDAPProvider()->getUserAttribute($user->getUID(), $property); + } +} diff --git a/apps/files_external/lib/Lib/Auth/Password/Password.php b/apps/files_external/lib/Lib/Auth/Password/Password.php new file mode 100644 index 00000000000..d4291148e3e --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/Password.php @@ -0,0 +1,29 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCP\IL10N; + +/** + * Basic password authentication mechanism + */ +class Password extends AuthMechanism { + public function __construct(IL10N $l) { + $this + ->setIdentifier('password::password') + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Login and password')) + ->addParameters([ + new DefinitionParameter('user', $l->t('Login')), + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD), + ]); + } +} diff --git a/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php b/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php new file mode 100644 index 00000000000..8f161073771 --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/SessionCredentials.php @@ -0,0 +1,67 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\SessionStorageWrapper; +use OCA\Files_External\Lib\StorageConfig; +use OCP\Authentication\Exceptions\CredentialsUnavailableException; +use OCP\Authentication\LoginCredentials\IStore as CredentialsStore; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageAuthException; +use OCP\IL10N; +use OCP\IUser; + +/** + * Username and password from login credentials, saved in session + */ +class SessionCredentials extends AuthMechanism { + + public function __construct( + IL10N $l, + private CredentialsStore $credentialsStore, + ) { + $this->setIdentifier('password::sessioncredentials') + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Log-in credentials, save in session')) + ->addParameters([ + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_HIDDEN) + ->setFlag(DefinitionParameter::FLAG_OPTIONAL), + ]); + } + + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { + try { + $credentials = $this->credentialsStore->getLoginCredentials(); + } catch (CredentialsUnavailableException $e) { + throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); + } + + if ($user === null) { + throw new StorageAuthException('Session unavailable'); + } + + if ($credentials->getUID() !== $user->getUID()) { + throw new StorageAuthException('Session credentials for storage owner not available'); + } + + $storage->setBackendOption('user', $credentials->getLoginName()); + $storage->setBackendOption('password', $credentials->getPassword()); + } + + public function wrapStorage(IStorage $storage): IStorage { + return new SessionStorageWrapper(['storage' => $storage]); + } +} diff --git a/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php b/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php new file mode 100644 index 00000000000..cb7165261ac --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/UserGlobalAuth.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\Service\BackendService; +use OCP\IL10N; +use OCP\IUser; +use OCP\Security\ICredentialsManager; + +/** + * User provided Global Username and Password + */ +class UserGlobalAuth extends AuthMechanism { + private const CREDENTIALS_IDENTIFIER = 'password::global'; + + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { + $this + ->setIdentifier('password::global::user') + ->setVisibility(BackendService::VISIBILITY_DEFAULT) + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Global credentials, manually entered')); + } + + public function saveBackendOptions(IUser $user, $id, $backendOptions) { + // backendOptions are set when invoked via Files app + // but they are not set when invoked via ext storage settings + if (!isset($backendOptions['user']) && !isset($backendOptions['password'])) { + return; + } + + if ($backendOptions['password'] === DefinitionParameter::UNMODIFIED_PLACEHOLDER) { + $oldCredentials = $this->credentialsManager->retrieve($user->getUID(), self::CREDENTIALS_IDENTIFIER); + $backendOptions['password'] = $oldCredentials['password']; + } + + // make sure we're not setting any unexpected keys + $credentials = [ + 'user' => $backendOptions['user'], + 'password' => $backendOptions['password'], + ]; + $this->credentialsManager->store($user->getUID(), self::CREDENTIALS_IDENTIFIER, $credentials); + } + + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { + if ($user === null) { + throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); + } + + $uid = $user->getUID(); + $credentials = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER); + + if (is_array($credentials)) { + $storage->setBackendOption('user', $credentials['user']); + $storage->setBackendOption('password', $credentials['password']); + } + } +} diff --git a/apps/files_external/lib/Lib/Auth/Password/UserProvided.php b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php new file mode 100644 index 00000000000..b158392f6eb --- /dev/null +++ b/apps/files_external/lib/Lib/Auth/Password/UserProvided.php @@ -0,0 +1,77 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2015 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Lib\Auth\Password; + +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\Auth\IUserProvided; +use OCA\Files_External\Lib\DefinitionParameter; +use OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; +use OCA\Files_External\Lib\StorageConfig; +use OCA\Files_External\Service\BackendService; +use OCP\IL10N; +use OCP\IUser; +use OCP\Security\ICredentialsManager; + +/** + * User provided Username and Password + */ +class UserProvided extends AuthMechanism implements IUserProvided { + public const CREDENTIALS_IDENTIFIER_PREFIX = 'password::userprovided/'; + + public function __construct( + IL10N $l, + protected ICredentialsManager $credentialsManager, + ) { + $this + ->setIdentifier('password::userprovided') + ->setVisibility(BackendService::VISIBILITY_ADMIN) + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Manually entered, store in database')) + ->addParameters([ + (new DefinitionParameter('user', $l->t('Login'))) + ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED), + (new DefinitionParameter('password', $l->t('Password'))) + ->setType(DefinitionParameter::VALUE_PASSWORD) + ->setFlag(DefinitionParameter::FLAG_USER_PROVIDED), + ]); + } + + private function getCredentialsIdentifier($storageId) { + return self::CREDENTIALS_IDENTIFIER_PREFIX . $storageId; + } + + public function saveBackendOptions(IUser $user, $mountId, array $options) { + if ($options['password'] === DefinitionParameter::UNMODIFIED_PLACEHOLDER) { + $oldCredentials = $this->credentialsManager->retrieve($user->getUID(), $this->getCredentialsIdentifier($mountId)); + $options['password'] = $oldCredentials['password']; + } + + $this->credentialsManager->store($user->getUID(), $this->getCredentialsIdentifier($mountId), [ + 'user' => $options['user'], // explicitly copy the fields we want instead of just passing the entire $options array + 'password' => $options['password'] // this way we prevent users from being able to modify any other field + ]); + } + + /** + * @return void + */ + public function manipulateStorageConfig(StorageConfig &$storage, ?IUser $user = null) { + if (!isset($user)) { + throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); + } + $uid = $user->getUID(); + $credentials = $this->credentialsManager->retrieve($uid, $this->getCredentialsIdentifier($storage->getId())); + + if (!isset($credentials)) { + throw new InsufficientDataForMeaningfulAnswerException('No credentials saved'); + } + + $storage->setBackendOption('user', $credentials['user']); + $storage->setBackendOption('password', $credentials['password']); + } +} |