349 lines
9.1 KiB
PHP
349 lines
9.1 KiB
PHP
<?php
|
|
/**
|
|
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
|
*
|
|
* @author Bjoern Schiessle <bjoern@schiessle.org>
|
|
* @author Björn Schießle <bjoern@schiessle.org>
|
|
* @author Clark Tomlinson <fallen013@gmail.com>
|
|
* @author Morris Jobke <hey@morrisjobke.de>
|
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
|
*
|
|
* @license AGPL-3.0
|
|
*
|
|
* This code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* 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, version 3,
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
namespace OCA\Encryption\Hooks;
|
|
|
|
|
|
use OC\Files\Filesystem;
|
|
use OCA\Encryption\Crypto\Crypt;
|
|
use OCA\Encryption\Hooks\Contracts\IHook;
|
|
use OCA\Encryption\KeyManager;
|
|
use OCA\Encryption\Recovery;
|
|
use OCA\Encryption\Session;
|
|
use OCA\Encryption\Users\Setup;
|
|
use OCA\Encryption\Util;
|
|
use OCP\Encryption\Exceptions\GenericEncryptionException;
|
|
use OCP\ILogger;
|
|
use OCP\IUserManager;
|
|
use OCP\IUserSession;
|
|
use OCP\Util as OCUtil;
|
|
|
|
class UserHooks implements IHook {
|
|
|
|
/**
|
|
* list of user for which we perform a password reset
|
|
* @var array
|
|
*/
|
|
protected static $passwordResetUsers = [];
|
|
|
|
/**
|
|
* @var KeyManager
|
|
*/
|
|
private $keyManager;
|
|
/**
|
|
* @var IUserManager
|
|
*/
|
|
private $userManager;
|
|
/**
|
|
* @var ILogger
|
|
*/
|
|
private $logger;
|
|
/**
|
|
* @var Setup
|
|
*/
|
|
private $userSetup;
|
|
/**
|
|
* @var IUserSession
|
|
*/
|
|
private $userSession;
|
|
/**
|
|
* @var Util
|
|
*/
|
|
private $util;
|
|
/**
|
|
* @var Session
|
|
*/
|
|
private $session;
|
|
/**
|
|
* @var Recovery
|
|
*/
|
|
private $recovery;
|
|
/**
|
|
* @var Crypt
|
|
*/
|
|
private $crypt;
|
|
|
|
/**
|
|
* UserHooks constructor.
|
|
*
|
|
* @param KeyManager $keyManager
|
|
* @param IUserManager $userManager
|
|
* @param ILogger $logger
|
|
* @param Setup $userSetup
|
|
* @param IUserSession $userSession
|
|
* @param Util $util
|
|
* @param Session $session
|
|
* @param Crypt $crypt
|
|
* @param Recovery $recovery
|
|
*/
|
|
public function __construct(KeyManager $keyManager,
|
|
IUserManager $userManager,
|
|
ILogger $logger,
|
|
Setup $userSetup,
|
|
IUserSession $userSession,
|
|
Util $util,
|
|
Session $session,
|
|
Crypt $crypt,
|
|
Recovery $recovery) {
|
|
|
|
$this->keyManager = $keyManager;
|
|
$this->userManager = $userManager;
|
|
$this->logger = $logger;
|
|
$this->userSetup = $userSetup;
|
|
$this->userSession = $userSession;
|
|
$this->util = $util;
|
|
$this->session = $session;
|
|
$this->recovery = $recovery;
|
|
$this->crypt = $crypt;
|
|
}
|
|
|
|
/**
|
|
* Connects Hooks
|
|
*
|
|
* @return null
|
|
*/
|
|
public function addHooks() {
|
|
OCUtil::connectHook('OC_User', 'post_login', $this, 'login');
|
|
OCUtil::connectHook('OC_User', 'logout', $this, 'logout');
|
|
|
|
// this hooks only make sense if no master key is used
|
|
if ($this->util->isMasterKeyEnabled() === false) {
|
|
OCUtil::connectHook('OC_User',
|
|
'post_setPassword',
|
|
$this,
|
|
'setPassphrase');
|
|
|
|
OCUtil::connectHook('OC_User',
|
|
'pre_setPassword',
|
|
$this,
|
|
'preSetPassphrase');
|
|
|
|
OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController',
|
|
'post_passwordReset',
|
|
$this,
|
|
'postPasswordReset');
|
|
|
|
OCUtil::connectHook('\OC\Core\LostPassword\Controller\LostController',
|
|
'pre_passwordReset',
|
|
$this,
|
|
'prePasswordReset');
|
|
|
|
OCUtil::connectHook('OC_User',
|
|
'post_createUser',
|
|
$this,
|
|
'postCreateUser');
|
|
|
|
OCUtil::connectHook('OC_User',
|
|
'post_deleteUser',
|
|
$this,
|
|
'postDeleteUser');
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Startup encryption backend upon user login
|
|
*
|
|
* @note This method should never be called for users using client side encryption
|
|
* @param array $params
|
|
* @return boolean|null
|
|
*/
|
|
public function login($params) {
|
|
// ensure filesystem is loaded
|
|
if (!\OC\Files\Filesystem::$loaded) {
|
|
$this->setupFS($params['uid']);
|
|
}
|
|
if ($this->util->isMasterKeyEnabled() === false) {
|
|
$this->userSetup->setupUser($params['uid'], $params['password']);
|
|
}
|
|
|
|
$this->keyManager->init($params['uid'], $params['password']);
|
|
}
|
|
|
|
/**
|
|
* remove keys from session during logout
|
|
*/
|
|
public function logout() {
|
|
$this->session->clear();
|
|
}
|
|
|
|
/**
|
|
* setup encryption backend upon user created
|
|
*
|
|
* @note This method should never be called for users using client side encryption
|
|
* @param array $params
|
|
*/
|
|
public function postCreateUser($params) {
|
|
$this->userSetup->setupUser($params['uid'], $params['password']);
|
|
}
|
|
|
|
/**
|
|
* cleanup encryption backend upon user deleted
|
|
*
|
|
* @param array $params : uid, password
|
|
* @note This method should never be called for users using client side encryption
|
|
*/
|
|
public function postDeleteUser($params) {
|
|
$this->keyManager->deletePublicKey($params['uid']);
|
|
}
|
|
|
|
public function prePasswordReset($params) {
|
|
$user = $params['uid'];
|
|
self::$passwordResetUsers[$user] = true;
|
|
}
|
|
|
|
public function postPasswordReset($params) {
|
|
$uid = $params['uid'];
|
|
$password = $params['password'];
|
|
$this->keyManager->backupUserKeys('passwordReset', $uid);
|
|
$this->keyManager->deleteUserKeys($uid);
|
|
$this->userSetup->setupUser($uid, $password);
|
|
unset(self::$passwordResetUsers[$uid]);
|
|
}
|
|
|
|
/**
|
|
* If the password can't be changed within Nextcloud, than update the key password in advance.
|
|
*
|
|
* @param array $params : uid, password
|
|
* @return boolean|null
|
|
*/
|
|
public function preSetPassphrase($params) {
|
|
$user = $this->userManager->get($params['uid']);
|
|
|
|
if ($user && !$user->canChangePassword()) {
|
|
$this->setPassphrase($params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Change a user's encryption passphrase
|
|
*
|
|
* @param array $params keys: uid, password
|
|
* @return boolean|null
|
|
*/
|
|
public function setPassphrase($params) {
|
|
|
|
// if we are in the process to resetting a user password, we have nothing
|
|
// to do here
|
|
if (isset(self::$passwordResetUsers[$params['uid']])) {
|
|
return true;
|
|
}
|
|
|
|
// Get existing decrypted private key
|
|
$user = $this->userSession->getUser();
|
|
|
|
// current logged in user changes his own password
|
|
if ($user && $params['uid'] === $user->getUID()) {
|
|
|
|
$privateKey = $this->session->getPrivateKey();
|
|
|
|
// Encrypt private key with new user pwd as passphrase
|
|
$encryptedPrivateKey = $this->crypt->encryptPrivateKey($privateKey, $params['password'], $params['uid']);
|
|
|
|
// Save private key
|
|
if ($encryptedPrivateKey) {
|
|
$this->keyManager->setPrivateKey($user->getUID(),
|
|
$this->crypt->generateHeader() . $encryptedPrivateKey);
|
|
} else {
|
|
$this->logger->error('Encryption could not update users encryption password');
|
|
}
|
|
|
|
// NOTE: Session does not need to be updated as the
|
|
// private key has not changed, only the passphrase
|
|
// used to decrypt it has changed
|
|
} else { // admin changed the password for a different user, create new keys and re-encrypt file keys
|
|
$userId = $params['uid'];
|
|
$this->initMountPoints($userId);
|
|
$recoveryPassword = isset($params['recoveryPassword']) ? $params['recoveryPassword'] : null;
|
|
|
|
$recoveryKeyId = $this->keyManager->getRecoveryKeyId();
|
|
$recoveryKey = $this->keyManager->getSystemPrivateKey($recoveryKeyId);
|
|
try {
|
|
$decryptedRecoveryKey = $this->crypt->decryptPrivateKey($recoveryKey, $recoveryPassword);
|
|
} catch (\Exception $e) {
|
|
$decryptedRecoveryKey = false;
|
|
}
|
|
if ($decryptedRecoveryKey === false) {
|
|
$message = 'Can not decrypt the recovery key. Maybe you provided the wrong password. Try again.';
|
|
throw new GenericEncryptionException($message, $message);
|
|
}
|
|
|
|
// we generate new keys if...
|
|
// ...we have a recovery password and the user enabled the recovery key
|
|
// ...encryption was activated for the first time (no keys exists)
|
|
// ...the user doesn't have any files
|
|
if (
|
|
($this->recovery->isRecoveryEnabledForUser($userId) && $recoveryPassword)
|
|
|| !$this->keyManager->userHasKeys($userId)
|
|
|| !$this->util->userHasFiles($userId)
|
|
) {
|
|
|
|
// backup old keys
|
|
//$this->backupAllKeys('recovery');
|
|
|
|
$newUserPassword = $params['password'];
|
|
|
|
$keyPair = $this->crypt->createKeyPair();
|
|
|
|
// Save public key
|
|
$this->keyManager->setPublicKey($userId, $keyPair['publicKey']);
|
|
|
|
// Encrypt private key with new password
|
|
$encryptedKey = $this->crypt->encryptPrivateKey($keyPair['privateKey'], $newUserPassword, $userId);
|
|
|
|
if ($encryptedKey) {
|
|
$this->keyManager->setPrivateKey($userId, $this->crypt->generateHeader() . $encryptedKey);
|
|
|
|
if ($recoveryPassword) { // if recovery key is set we can re-encrypt the key files
|
|
$this->recovery->recoverUsersFiles($recoveryPassword, $userId);
|
|
}
|
|
} else {
|
|
$this->logger->error('Encryption Could not update users encryption password');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* init mount points for given user
|
|
*
|
|
* @param string $user
|
|
* @throws \OC\User\NoUserException
|
|
*/
|
|
protected function initMountPoints($user) {
|
|
Filesystem::initMountPoints($user);
|
|
}
|
|
|
|
/**
|
|
* setup file system for user
|
|
*
|
|
* @param string $uid user id
|
|
*/
|
|
protected function setupFS($uid) {
|
|
\OC_Util::setupFS($uid);
|
|
}
|
|
}
|