Skip to content

Commit

Permalink
Merge pull request #21037 from nextcloud/login-credentails-save
Browse files Browse the repository at this point in the history
Only save login credentials in database once there is an external storage that needs it
  • Loading branch information
blizzz authored Aug 7, 2020
2 parents 0807e29 + 0b0cc48 commit a2c262b
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 26 deletions.
4 changes: 4 additions & 0 deletions apps/files_external/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ External storage can be configured using the GUI or at the command line. This se
<nextcloud min-version="20" max-version="20"/>
</dependencies>

<background-jobs>
<job>OCA\Files_External\BackgroundJob\CredentialsCleanup</job>
</background-jobs>

<commands>
<command>OCA\Files_External\Command\ListCommand</command>
<command>OCA\Files_External\Command\Config</command>
Expand Down
69 changes: 69 additions & 0 deletions apps/files_external/lib/BackgroundJob/CredentialsCleanup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Robin Appelman <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* 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 OCA\Files_External\BackgroundJob;

use OCA\Files_External\Lib\Auth\Password\LoginCredentials;
use OCA\Files_External\Lib\StorageConfig;
use OCA\Files_External\Service\UserGlobalStoragesService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Security\ICredentialsManager;
use OCP\IUser;
use OCP\IUserManager;

class CredentialsCleanup extends TimedJob {
private $credentialsManager;
private $userGlobalStoragesService;
private $userManager;

public function __construct(
ITimeFactory $time,
ICredentialsManager $credentialsManager,
UserGlobalStoragesService $userGlobalStoragesService,
IUserManager $userManager
) {
parent::__construct($time);

$this->credentialsManager = $credentialsManager;
$this->userGlobalStoragesService = $userGlobalStoragesService;
$this->userManager = $userManager;

// run every day
$this->setInterval(24 * 60 * 60);
}

protected function run($argument) {
$this->userManager->callForSeenUsers(function (IUser $user) {
$storages = $this->userGlobalStoragesService->getAllStoragesForUser($user);

$usesLoginCredentials = array_reduce($storages, function (bool $uses, StorageConfig $storage) {
return $uses || $storage->getAuthMechanism() instanceof LoginCredentials;
}, false);

if (!$usesLoginCredentials) {
$this->credentialsManager->delete($user->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER);
}
});
}
}
57 changes: 35 additions & 22 deletions apps/files_external/lib/Lib/Auth/Password/LoginCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,16 @@
use OCA\Files_External\Lib\Auth\AuthMechanism;
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\Security\ICredentialsManager;
use OCP\User\Events\PasswordUpdatedEvent;
use OCP\User\Events\UserLoggedInEvent;

/**
* Username and password from login credentials, saved in DB
Expand All @@ -44,45 +50,52 @@ class LoginCredentials extends AuthMechanism {
/** @var ICredentialsManager */
protected $credentialsManager;

public function __construct(IL10N $l, ISession $session, ICredentialsManager $credentialsManager) {
/** @var CredentialsStore */
private $credentialsStore;

public function __construct(IL10N $l, ISession $session, ICredentialsManager $credentialsManager, CredentialsStore $credentialsStore, IEventDispatcher $eventDispatcher) {
$this->session = $session;
$this->credentialsManager = $credentialsManager;
$this->credentialsStore = $credentialsStore;

$this
->setIdentifier('password::logincredentials')
->setScheme(self::SCHEME_PASSWORD)
->setText($l->t('Log-in credentials, save in database'))
->addParameters([
])
;
]);

\OCP\Util::connectHook('OC_User', 'post_login', $this, 'authenticate');
$eventDispatcher->addServiceListener(UserLoggedInEvent::class, StorePasswordListener::class);
$eventDispatcher->addServiceListener(PasswordUpdatedEvent::class, StorePasswordListener::class);
}

/**
* Hook listener on post login
*
* @param array $params
*/
public function authenticate(array $params) {
$userId = $params['uid'];
$credentials = [
'user' => $this->session->get('loginname'),
'password' => $params['password']
];
$this->credentialsManager->store($userId, self::CREDENTIALS_IDENTIFIER, $credentials);
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();

$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;
}

public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) {
if (!isset($user)) {
throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved');
}
$uid = $user->getUID();
$credentials = $this->credentialsManager->retrieve($uid, self::CREDENTIALS_IDENTIFIER);

if (!isset($credentials)) {
throw new InsufficientDataForMeaningfulAnswerException('No login credentials saved');
}
$credentials = $this->getCredentials($user);

$storage->setBackendOption('user', $credentials['user']);
$storage->setBackendOption('password', $credentials['password']);
Expand Down
64 changes: 64 additions & 0 deletions apps/files_external/lib/Listener/StorePasswordListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

/**
* @copyright Copyright (c) 2020, Morris Jobke <[email protected]>
*
* @author Morris Jobke <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* 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 OCA\Files_External\Listener;

use OCA\Files_External\Lib\Auth\Password\LoginCredentials;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Security\ICredentialsManager;
use OCP\User\Events\PasswordUpdatedEvent;
use OCP\User\Events\UserLoggedInEvent;

class StorePasswordListener implements IEventListener {
/** @var ICredentialsManager */
private $credentialsManager;

public function __construct(ICredentialsManager $credentialsManager) {
$this->credentialsManager = $credentialsManager;
}

public function handle(Event $event): void {
if (!$event instanceof UserLoggedInEvent && !$event instanceof PasswordUpdatedEvent) {
return;
}

if ($event instanceof UserLoggedInEvent && $event->isTokenLogin()) {
return;
}

$stored = $this->credentialsManager->retrieve($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER);

if ($stored && $stored['password'] !== $event->getPassword()) {
$credentials = [
'user' => $stored['user'],
'password' => $event->getPassword()
];

$this->credentialsManager->store($event->getUser()->getUID(), LoginCredentials::CREDENTIALS_IDENTIFIER, $credentials);
}
}
}
13 changes: 9 additions & 4 deletions apps/files_external/lib/Service/UserGlobalStoragesService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use OCA\Files_External\Lib\StorageConfig;
use OCP\Files\Config\IUserMountCache;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;

/**
Expand Down Expand Up @@ -177,14 +178,18 @@ protected function isApplicable(StorageConfig $config) {
/**
* Gets all storages for the user, admin, personal, global, etc
*
* @param IUser|null $user user to get the storages for, if not set the currently logged in user will be used
* @return StorageConfig[] array of storage configs
*/
public function getAllStoragesForUser() {
if (is_null($this->getUser())) {
public function getAllStoragesForUser(IUser $user = null) {
if (is_null($user)) {
$user = $this->getUser();
}
if (is_null($user)) {
return [];
}
$groupIds = $this->groupManager->getUserGroupIds($this->getUser());
$mounts = $this->dbConfig->getMountsForUser($this->getUser()->getUID(), $groupIds);
$groupIds = $this->groupManager->getUserGroupIds($user);
$mounts = $this->dbConfig->getMountsForUser($user->getUID(), $groupIds);
$configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts);
$configs = array_filter($configs, function ($config) {
return $config instanceof StorageConfig;
Expand Down

0 comments on commit a2c262b

Please sign in to comment.