Introduce CredentialsManager for storage of credentials in DB

CredentialsManager performs a simple role, of storing and retrieving
encrypted credentials from the database. Credentials are stored by user
ID (which may be null) and credentials identifier. Credentials
themselves may be of any type that can be JSON encoded.

The rationale behind this is to avoid further (mis)use of
oc_preferences, which was being used for all manner of data not related
to user preferences.
This commit is contained in:
Robin McCorkell 2015-08-24 16:13:16 +01:00 committed by Robin Appelman
parent 88cd615214
commit da4127d23b
7 changed files with 366 additions and 0 deletions

View File

@ -1493,5 +1493,60 @@
</table>
<table>
<!--
Encrypted credentials storage
-->
<name>*dbprefix*credentials</name>
<declaration>
<field>
<name>user</name>
<type>text</type>
<default></default>
<notnull>false</notnull>
<length>64</length>
</field>
<field>
<name>identifier</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>credentials</name>
<type>clob</type>
<notnull>false</notnull>
</field>
<index>
<name>credentials_user_id</name>
<primary>true</primary>
<unique>true</unique>
<field>
<name>user</name>
<sorting>ascending</sorting>
</field>
<field>
<name>identifier</name>
<sorting>ascending</sorting>
</field>
</index>
<index>
<name>credentials_user</name>
<unique>false</unique>
<field>
<name>user</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>
</database>

View File

@ -215,6 +215,10 @@ class DIContainer extends SimpleContainer implements IAppContainer {
return $this->getServer()->getHasher();
});
$this->registerService('OCP\\Security\\ICredentialsManager', function($c) {
return $this->getServer()->getCredentialsManager();
});
$this->registerService('OCP\\Security\\ISecureRandom', function($c) {
return $this->getServer()->getSecureRandom();
});

View File

@ -0,0 +1,125 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @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 OC\Security;
use OCP\Security\ICrypto;
use OCP\IDBConnection;
use OCP\Security\ICredentialsManager;
use OCP\IConfig;
/**
* Store and retrieve credentials for external services
*
* @package OC\Security
*/
class CredentialsManager implements ICredentialsManager {
const DB_TABLE = 'credentials';
/** @var ICrypto */
protected $crypto;
/** @var IDBConnection */
protected $dbConnection;
/**
* @param ICrypto $crypto
* @param IDBConnection $dbConnection
*/
public function __construct(ICrypto $crypto, IDBConnection $dbConnection) {
$this->crypto = $crypto;
$this->dbConnection = $dbConnection;
}
/**
* Store a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @param mixed $credentials
*/
public function store($userId, $identifier, $credentials) {
$value = $this->crypto->encrypt(json_encode($credentials));
$this->dbConnection->setValues(self::DB_TABLE, [
'user' => $userId,
'identifier' => $identifier,
], [
'credentials' => $value,
]);
}
/**
* Retrieve a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @return mixed
*/
public function retrieve($userId, $identifier) {
$qb = $this->dbConnection->getQueryBuilder();
$qb->select('credentials')
->from(self::DB_TABLE)
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)))
;
$result = $qb->execute()->fetch();
if (!$result) {
return null;
}
$value = $result['credentials'];
return json_decode($this->crypto->decrypt($value), true);
}
/**
* Delete a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @return int rows removed
*/
public function delete($userId, $identifier) {
$qb = $this->dbConnection->getQueryBuilder();
$qb->delete(self::DB_TABLE)
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
->andWhere($qb->expr()->eq('identifier', $qb->createNamedParameter($identifier)))
;
return $qb->execute();
}
/**
* Erase all credentials stored for a user
*
* @param string $userId
* @return int rows removed
*/
public function erase($userId) {
$qb = $this->dbConnection->getQueryBuilder();
$qb->delete(self::DB_TABLE)
->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
;
return $qb->execute();
}
}

View File

@ -63,6 +63,7 @@ use OC\Notification\Manager;
use OC\Security\CertificateManager;
use OC\Security\Crypto;
use OC\Security\Hasher;
use OC\Security\CredentialsManager;
use OC\Security\SecureRandom;
use OC\Security\TrustedDomainHelper;
use OC\Session\CryptoWrapper;
@ -332,6 +333,9 @@ class Server extends ServerContainer implements IServerContainer {
$this->registerService('Hasher', function (Server $c) {
return new Hasher($c->getConfig());
});
$this->registerService('CredentialsManager', function (Server $c) {
return new CredentialsManager($c->getCrypto(), $c->getDatabaseConnection());
});
$this->registerService('DatabaseConnection', function (Server $c) {
$factory = new \OC\DB\ConnectionFactory();
$systemConfig = $c->getSystemConfig();
@ -919,6 +923,15 @@ class Server extends ServerContainer implements IServerContainer {
return $this->query('Hasher');
}
/**
* Returns a CredentialsManager instance
*
* @return \OCP\Security\ICredentialsManager
*/
public function getCredentialsManager() {
return $this->query('CredentialsManager');
}
/**
* Returns an instance of the db facade
* @deprecated use getDatabaseConnection, will be removed in ownCloud 10

View File

@ -180,6 +180,14 @@ interface IServerContainer {
*/
public function getSecureRandom();
/**
* Returns a CredentialsManager instance
*
* @return \OCP\Security\ICredentialsManager
* @since 9.0.0
*/
public function getCredentialsManager();
/**
* Returns an instance of the db facade
* @deprecated 8.1.0 use getDatabaseConnection, will be removed in ownCloud 10

View File

@ -0,0 +1,71 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @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 OCP\Security;
/**
* Store and retrieve credentials for external services
*
* @package OCP\Security
* @since 8.2.0
*/
interface ICredentialsManager {
/**
* Store a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @param mixed $credentials
* @since 8.2.0
*/
public function store($userId, $identifier, $credentials);
/**
* Retrieve a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @return mixed
* @since 8.2.0
*/
public function retrieve($userId, $identifier);
/**
* Delete a set of credentials
*
* @param string|null $userId Null for system-wide credentials
* @param string $identifier
* @return int rows removed
* @since 8.2.0
*/
public function delete($userId, $identifier);
/**
* Erase all credentials stored for a user
*
* @param string $userId
* @return int rows removed
* @since 8.2.0
*/
public function erase($userId);
}

View File

@ -0,0 +1,90 @@
<?php
/**
* @author Robin McCorkell <rmccorkell@owncloud.com>
*
* @copyright Copyright (c) 2015, ownCloud, Inc.
* @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/>
*
*/
use \OCP\Security\ICrypto;
use \OCP\IDBConnection;
use \OC\Security\CredentialsManager;
class CredentialsManagerTest extends \Test\TestCase {
/** @var ICrypto */
protected $crypto;
/** @var IDBConnection */
protected $dbConnection;
/** @var CredentialsManager */
protected $manager;
protected function setUp() {
parent::setUp();
$this->crypto = $this->getMock('\OCP\Security\ICrypto');
$this->dbConnection = $this->getMockBuilder('\OC\DB\Connection')
->disableOriginalConstructor()
->getMock();
$this->manager = new CredentialsManager($this->crypto, $this->dbConnection);
}
public function testStore() {
$userId = 'abc';
$identifier = 'foo';
$credentials = 'bar';
$this->crypto->expects($this->once())
->method('encrypt')
->with(json_encode($credentials))
->willReturn('baz');
$this->dbConnection->expects($this->once())
->method('setValues')
->with(CredentialsManager::DB_TABLE,
['user' => $userId, 'identifier' => $identifier],
['credentials' => 'baz']
);
$this->manager->store($userId, $identifier, $credentials);
}
public function testRetrieve() {
$userId = 'abc';
$identifier = 'foo';
$this->crypto->expects($this->once())
->method('decrypt')
->with('baz')
->willReturn(json_encode('bar'));
$qb = $this->getMockBuilder('\OC\DB\QueryBuilder\QueryBuilder')
->setConstructorArgs([$this->dbConnection])
->setMethods(['execute'])
->getMock();
$qb->expects($this->once())
->method('execute')
->willReturn(['credentials' => 'baz']);
$this->dbConnection->expects($this->once())
->method('getQueryBuilder')
->willReturn($qb);
$this->manager->retrieve($userId, $identifier);
}
}