diff options
44 files changed, 957 insertions, 126 deletions
diff --git a/apps/files_external/appinfo/application.php b/apps/files_external/appinfo/application.php index c755b6a29b0..1571178596b 100644 --- a/apps/files_external/appinfo/application.php +++ b/apps/files_external/appinfo/application.php @@ -108,6 +108,7 @@ class Application extends App { // AuthMechanism::SCHEME_PASSWORD mechanisms $container->query('OCA\Files_External\Lib\Auth\Password\Password'), $container->query('OCA\Files_External\Lib\Auth\Password\SessionCredentials'), + $container->query('OCA\Files_External\Lib\Auth\Password\LoginCredentials'), // AuthMechanism::SCHEME_OAUTH1 mechanisms $container->query('OCA\Files_External\Lib\Auth\OAuth1\OAuth1'), diff --git a/apps/files_external/appinfo/info.xml b/apps/files_external/appinfo/info.xml index 1a9fa73de3f..1cd4f602075 100644 --- a/apps/files_external/appinfo/info.xml +++ b/apps/files_external/appinfo/info.xml @@ -13,7 +13,7 @@ <admin>admin-external-storage</admin> </documentation> <rememberlogin>false</rememberlogin> - <version>0.5.1</version> + <version>0.5.2</version> <types> <filesystem/> </types> diff --git a/apps/files_external/controller/storagescontroller.php b/apps/files_external/controller/storagescontroller.php index 07e2e69f601..64b989f0c77 100644 --- a/apps/files_external/controller/storagescontroller.php +++ b/apps/files_external/controller/storagescontroller.php @@ -212,6 +212,15 @@ abstract class StoragesController extends Controller { return null; } + protected function manipulateStorageConfig(StorageConfig $storage) { + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + $authMechanism->manipulateStorageConfig($storage); + /** @var Backend */ + $backend = $storage->getBackend(); + $backend->manipulateStorageConfig($storage); + } + /** * Check whether the given storage is available / valid. * @@ -222,13 +231,10 @@ abstract class StoragesController extends Controller { */ protected function updateStorageStatus(StorageConfig &$storage) { try { - /** @var AuthMechanism */ - $authMechanism = $storage->getAuthMechanism(); - $authMechanism->manipulateStorageConfig($storage); + $this->manipulateStorageConfig($storage); + /** @var Backend */ $backend = $storage->getBackend(); - $backend->manipulateStorageConfig($storage); - // update status (can be time-consuming) $storage->setStatus( \OC_Mount_Config::getBackendStatus( diff --git a/apps/files_external/controller/userglobalstoragescontroller.php b/apps/files_external/controller/userglobalstoragescontroller.php index 5aea7875ed4..6d4548754df 100644 --- a/apps/files_external/controller/userglobalstoragescontroller.php +++ b/apps/files_external/controller/userglobalstoragescontroller.php @@ -21,6 +21,7 @@ namespace OCA\Files_External\Controller; +use OCA\Files_External\Lib\Auth\AuthMechanism; use \OCP\IRequest; use \OCP\IL10N; use \OCP\AppFramework\Http\DataResponse; @@ -30,24 +31,32 @@ use \OCA\Files_external\Service\UserGlobalStoragesService; use \OCA\Files_external\NotFoundException; use \OCA\Files_external\Lib\StorageConfig; use \OCA\Files_External\Lib\Backend\Backend; +use OCP\IUserSession; /** * User global storages controller */ class UserGlobalStoragesController extends StoragesController { /** + * @var IUserSession + */ + private $userSession; + + /** * Creates a new user global storages controller. * * @param string $AppName application name * @param IRequest $request request object * @param IL10N $l10n l10n service * @param UserGlobalStoragesService $userGlobalStoragesService storage service + * @param IUserSession $userSession */ public function __construct( $AppName, IRequest $request, IL10N $l10n, - UserGlobalStoragesService $userGlobalStoragesService + UserGlobalStoragesService $userGlobalStoragesService, + IUserSession $userSession ) { parent::__construct( $AppName, @@ -55,6 +64,7 @@ class UserGlobalStoragesController extends StoragesController { $l10n, $userGlobalStoragesService ); + $this->userSession = $userSession; } /** @@ -78,6 +88,15 @@ class UserGlobalStoragesController extends StoragesController { ); } + protected function manipulateStorageConfig(StorageConfig $storage) { + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + $authMechanism->manipulateStorageConfig($storage, $this->userSession->getUser()); + /** @var Backend */ + $backend = $storage->getBackend(); + $backend->manipulateStorageConfig($storage, $this->userSession->getUser()); + } + /** * Get an external storage entry. * diff --git a/apps/files_external/controller/userstoragescontroller.php b/apps/files_external/controller/userstoragescontroller.php index 345e4bf652b..741e906dec1 100644 --- a/apps/files_external/controller/userstoragescontroller.php +++ b/apps/files_external/controller/userstoragescontroller.php @@ -23,6 +23,7 @@ namespace OCA\Files_External\Controller; +use OCA\Files_External\Lib\Auth\AuthMechanism; use \OCP\IConfig; use \OCP\IUserSession; use \OCP\IRequest; @@ -41,18 +42,25 @@ use \OCA\Files_External\Lib\Backend\Backend; */ class UserStoragesController extends StoragesController { /** + * @var IUserSession + */ + private $userSession; + + /** * Creates a new user storages controller. * * @param string $AppName application name * @param IRequest $request request object * @param IL10N $l10n l10n service * @param UserStoragesService $userStoragesService storage service + * @param IUserSession $userSession */ public function __construct( $AppName, IRequest $request, IL10N $l10n, - UserStoragesService $userStoragesService + UserStoragesService $userStoragesService, + IUserSession $userSession ) { parent::__construct( $AppName, @@ -60,6 +68,16 @@ class UserStoragesController extends StoragesController { $l10n, $userStoragesService ); + $this->userSession = $userSession; + } + + protected function manipulateStorageConfig(StorageConfig $storage) { + /** @var AuthMechanism */ + $authMechanism = $storage->getAuthMechanism(); + $authMechanism->manipulateStorageConfig($storage, $this->userSession->getUser()); + /** @var Backend */ + $backend = $storage->getBackend(); + $backend->manipulateStorageConfig($storage, $this->userSession->getUser()); } /** diff --git a/apps/files_external/lib/auth/password/logincredentials.php b/apps/files_external/lib/auth/password/logincredentials.php new file mode 100644 index 00000000000..99cac3f4202 --- /dev/null +++ b/apps/files_external/lib/auth/password/logincredentials.php @@ -0,0 +1,92 @@ +<?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 OCA\Files_External\Lib\Auth\Password; + +use \OCP\IL10N; +use \OCP\IUser; +use \OCA\Files_External\Lib\DefinitionParameter; +use \OCA\Files_External\Lib\Auth\AuthMechanism; +use \OCA\Files_External\Lib\StorageConfig; +use \OCP\ISession; +use \OCP\Security\ICredentialsManager; +use \OCP\Files\Storage; +use \OCA\Files_External\Lib\SessionStorageWrapper; +use \OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; + +/** + * Username and password from login credentials, saved in DB + */ +class LoginCredentials extends AuthMechanism { + + const CREDENTIALS_IDENTIFIER = 'password::logincredentials/credentials'; + + /** @var ISession */ + protected $session; + + /** @var ICredentialsManager */ + protected $credentialsManager; + + public function __construct(IL10N $l, ISession $session, ICredentialsManager $credentialsManager) { + $this->session = $session; + $this->credentialsManager = $credentialsManager; + + $this + ->setIdentifier('password::logincredentials') + ->setScheme(self::SCHEME_PASSWORD) + ->setText($l->t('Login credentials')) + ->addParameters([ + ]) + ; + + \OCP\Util::connectHook('OC_User', 'post_login', $this, 'authenticate'); + } + + /** + * 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); + } + + 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'); + } + + $storage->setBackendOption('user', $credentials['user']); + $storage->setBackendOption('password', $credentials['password']); + } + +} diff --git a/apps/files_external/lib/auth/password/sessioncredentials.php b/apps/files_external/lib/auth/password/sessioncredentials.php index 4f7d24c2f60..3fb8b8526cc 100644 --- a/apps/files_external/lib/auth/password/sessioncredentials.php +++ b/apps/files_external/lib/auth/password/sessioncredentials.php @@ -21,6 +21,7 @@ namespace OCA\Files_External\Lib\Auth\Password; +use \OCP\IUser; use \OCP\IL10N; use \OCA\Files_External\Lib\DefinitionParameter; use \OCA\Files_External\Lib\Auth\AuthMechanism; @@ -66,7 +67,7 @@ class SessionCredentials extends AuthMechanism { $this->session->set('password::sessioncredentials/credentials', $this->crypto->encrypt(json_encode($params))); } - public function manipulateStorageConfig(StorageConfig &$storage) { + public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $encrypted = $this->session->get('password::sessioncredentials/credentials'); if (!isset($encrypted)) { throw new InsufficientDataForMeaningfulAnswerException('No session credentials saved'); diff --git a/apps/files_external/lib/auth/publickey/rsa.php b/apps/files_external/lib/auth/publickey/rsa.php index 131b3f36526..9045f6818f9 100644 --- a/apps/files_external/lib/auth/publickey/rsa.php +++ b/apps/files_external/lib/auth/publickey/rsa.php @@ -26,6 +26,7 @@ use \OCA\Files_External\Lib\DefinitionParameter; use \OCA\Files_External\Lib\Auth\AuthMechanism; use \OCA\Files_External\Lib\StorageConfig; use \OCP\IConfig; +use OCP\IUser; use \phpseclib\Crypt\RSA as RSACrypt; /** @@ -55,7 +56,7 @@ class RSA extends AuthMechanism { ; } - public function manipulateStorageConfig(StorageConfig &$storage) { + public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $auth = new RSACrypt(); $auth->setPassword($this->config->getSystemValue('secret', '')); if (!$auth->loadKey($storage->getBackendOption('private_key'))) { diff --git a/apps/files_external/lib/backend/smb.php b/apps/files_external/lib/backend/smb.php index aaf7658751f..9b71636936a 100644 --- a/apps/files_external/lib/backend/smb.php +++ b/apps/files_external/lib/backend/smb.php @@ -30,6 +30,7 @@ use \OCA\Files_External\Lib\StorageConfig; use \OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; use \OCA\Files_External\Lib\Auth\Password\Password; +use OCP\IUser; class SMB extends Backend { @@ -56,8 +57,9 @@ class SMB extends Backend { /** * @param StorageConfig $storage + * @param IUser $user */ - public function manipulateStorageConfig(StorageConfig &$storage) { + public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $user = $storage->getBackendOption('user'); if ($domain = $storage->getBackendOption('domain')) { $storage->setBackendOption('user', $domain.'\\'.$user); diff --git a/apps/files_external/lib/backend/smb_oc.php b/apps/files_external/lib/backend/smb_oc.php index 57fdfc30ff3..ba38754ce5a 100644 --- a/apps/files_external/lib/backend/smb_oc.php +++ b/apps/files_external/lib/backend/smb_oc.php @@ -30,6 +30,7 @@ use \OCA\Files_External\Lib\Auth\Password\SessionCredentials; use \OCA\Files_External\Lib\StorageConfig; use \OCA\Files_External\Lib\LegacyDependencyCheckPolyfill; use \OCA\Files_External\Lib\Backend\SMB; +use OCP\IUser; /** * Deprecated SMB_OC class - use SMB with the password::sessioncredentials auth mechanism @@ -59,7 +60,7 @@ class SMB_OC extends Backend { ; } - public function manipulateStorageConfig(StorageConfig &$storage) { + public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { $username_as_share = ($storage->getBackendOption('username_as_share') === true); if ($username_as_share) { diff --git a/apps/files_external/lib/config/configadapter.php b/apps/files_external/lib/config/configadapter.php index 0cd1381c815..2bf39bcaa4f 100644 --- a/apps/files_external/lib/config/configadapter.php +++ b/apps/files_external/lib/config/configadapter.php @@ -85,8 +85,8 @@ class ConfigAdapter implements IMountProvider { $storage->setBackendOption('objectstore', new $objectClass($objectStore)); } - $storage->getAuthMechanism()->manipulateStorageConfig($storage); - $storage->getBackend()->manipulateStorageConfig($storage); + $storage->getAuthMechanism()->manipulateStorageConfig($storage, $user); + $storage->getBackend()->manipulateStorageConfig($storage, $user); } /** diff --git a/apps/files_external/lib/storagemodifiertrait.php b/apps/files_external/lib/storagemodifiertrait.php index ec2b0a14ab1..30c2108feec 100644 --- a/apps/files_external/lib/storagemodifiertrait.php +++ b/apps/files_external/lib/storagemodifiertrait.php @@ -21,6 +21,7 @@ namespace OCA\Files_External\Lib; +use \OCP\IUser; use \OCP\Files\Storage; use \OCA\Files_External\Lib\StorageConfig; use \OCA\Files_External\Lib\InsufficientDataForMeaningfulAnswerException; @@ -45,10 +46,11 @@ trait StorageModifierTrait { * Modify a StorageConfig parameters * * @param StorageConfig $storage + * @param IUser $user User the storage is being used as * @throws InsufficientDataForMeaningfulAnswerException * @throws StorageNotAvailableException */ - public function manipulateStorageConfig(StorageConfig &$storage) { + public function manipulateStorageConfig(StorageConfig &$storage, IUser $user = null) { } /** diff --git a/apps/files_external/tests/controller/userstoragescontrollertest.php b/apps/files_external/tests/controller/userstoragescontrollertest.php index dd761fa9767..671e019fea0 100644 --- a/apps/files_external/tests/controller/userstoragescontrollertest.php +++ b/apps/files_external/tests/controller/userstoragescontrollertest.php @@ -48,7 +48,8 @@ class UserStoragesControllerTest extends StoragesControllerTest { 'files_external', $this->getMock('\OCP\IRequest'), $this->getMock('\OCP\IL10N'), - $this->service + $this->service, + $this->getMock('\OCP\IUserSession') ); } diff --git a/core/command/integrity/signapp.php b/core/command/integrity/signapp.php index a203b9ad1da..53df9619c6d 100644 --- a/core/command/integrity/signapp.php +++ b/core/command/integrity/signapp.php @@ -23,6 +23,7 @@ namespace OC\Core\Command\Integrity; use OC\IntegrityCheck\Checker; use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OCP\IURLGenerator; use phpseclib\Crypt\RSA; use phpseclib\File\X509; use Symfony\Component\Console\Command\Command; @@ -40,23 +41,28 @@ class SignApp extends Command { private $checker; /** @var FileAccessHelper */ private $fileAccessHelper; + /** @var IURLGenerator */ + private $urlGenerator; /** * @param Checker $checker * @param FileAccessHelper $fileAccessHelper + * @param IURLGenerator $urlGenerator */ public function __construct(Checker $checker, - FileAccessHelper $fileAccessHelper) { + FileAccessHelper $fileAccessHelper, + IURLGenerator $urlGenerator) { parent::__construct(null); $this->checker = $checker; $this->fileAccessHelper = $fileAccessHelper; + $this->urlGenerator = $urlGenerator; } protected function configure() { $this ->setName('integrity:sign-app') - ->setDescription('Sign app using a private key.') - ->addOption('appId', null, InputOption::VALUE_REQUIRED, 'Application to sign') + ->setDescription('Signs an app using a private key.') + ->addOption('path', null, InputOption::VALUE_REQUIRED, 'Application to sign') ->addOption('privateKey', null, InputOption::VALUE_REQUIRED, 'Path to private key to use for signing') ->addOption('certificate', null, InputOption::VALUE_REQUIRED, 'Path to certificate to use for signing'); } @@ -65,11 +71,14 @@ class SignApp extends Command { * {@inheritdoc } */ protected function execute(InputInterface $input, OutputInterface $output) { - $appId = $input->getOption('appId'); + $path = $input->getOption('path'); $privateKeyPath = $input->getOption('privateKey'); $keyBundlePath = $input->getOption('certificate'); - if(is_null($appId) || is_null($privateKeyPath) || is_null($keyBundlePath)) { - $output->writeln('--appId, --privateKey and --certificate are required.'); + if(is_null($path) || is_null($privateKeyPath) || is_null($keyBundlePath)) { + $documentationUrl = $this->urlGenerator->linkToDocs('developer-code-integrity'); + $output->writeln('This command requires the --path, --privateKey and --certificate.'); + $output->writeln('Example: ./occ integrity:sign-app --path="/Users/lukasreschke/Programming/myapp/" --privateKey="/Users/lukasreschke/private/myapp.key" --certificate="/Users/lukasreschke/public/mycert.crt"'); + $output->writeln('For more information please consult the documentation: '. $documentationUrl); return null; } @@ -91,8 +100,8 @@ class SignApp extends Command { $x509 = new X509(); $x509->loadX509($keyBundle); $x509->setPrivateKey($rsa); - $this->checker->writeAppSignature($appId, $x509, $rsa); + $this->checker->writeAppSignature($path, $x509, $rsa); - $output->writeln('Successfully signed "'.$appId.'"'); + $output->writeln('Successfully signed "'.$path.'"'); } } diff --git a/core/controller/setupcontroller.php b/core/controller/setupcontroller.php index 16646b97ae6..f25c6f39a0b 100644 --- a/core/controller/setupcontroller.php +++ b/core/controller/setupcontroller.php @@ -99,6 +99,7 @@ class SetupController { if( file_exists( $this->autoConfigFile )) { unlink($this->autoConfigFile); } + \OC::$server->getIntegrityCodeChecker()->runInstanceVerification(); \OC_Util::redirectToDefaultPage(); } diff --git a/core/register_command.php b/core/register_command.php index e43994530b9..5f9a2675873 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -36,7 +36,8 @@ $application->add(new OC\Core\Command\App\CheckCode($infoParser)); $application->add(new OC\Core\Command\L10n\CreateJs()); $application->add(new \OC\Core\Command\Integrity\SignApp( \OC::$server->getIntegrityCodeChecker(), - new \OC\IntegrityCheck\Helpers\FileAccessHelper() + new \OC\IntegrityCheck\Helpers\FileAccessHelper(), + \OC::$server->getURLGenerator() )); $application->add(new \OC\Core\Command\Integrity\SignCore( \OC::$server->getIntegrityCodeChecker(), diff --git a/db_structure.xml b/db_structure.xml index 058322f78a3..99bfa519b40 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1580,5 +1580,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> diff --git a/lib/private/allconfig.php b/lib/private/allconfig.php index af7ffa4168e..3c85bfacbb8 100644 --- a/lib/private/allconfig.php +++ b/lib/private/allconfig.php @@ -205,57 +205,28 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); - // Check if the key does exist - $sql = 'SELECT `configvalue` FROM `*PREFIX*preferences` '. - 'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ?'; - $result = $this->connection->executeQuery($sql, array($userId, $appName, $key)); - $oldValue = $result->fetchColumn(); - $result->closeCursor(); - $exists = $oldValue !== false; - - if($oldValue === strval($value)) { - // no changes - return; + $preconditionArray = []; + if (isset($preCondition)) { + $preconditionArray = [ + 'configvalue' => $preCondition, + ]; } - $affectedRows = 0; - if (!$exists && $preCondition === null) { - $this->connection->insertIfNotExist('*PREFIX*preferences', [ - 'configvalue' => $value, - 'userid' => $userId, - 'appid' => $appName, - 'configkey' => $key, - ], ['configkey', 'userid', 'appid']); - $affectedRows = 1; - } elseif ($exists) { - $data = array($value, $userId, $appName, $key); - - $sql = 'UPDATE `*PREFIX*preferences` SET `configvalue` = ? '. - 'WHERE `userid` = ? AND `appid` = ? AND `configkey` = ? '; - - if($preCondition !== null) { - if($this->getSystemValue('dbtype', 'sqlite') === 'oci') { - //oracle hack: need to explicitly cast CLOB to CHAR for comparison - $sql .= 'AND to_char(`configvalue`) = ?'; - } else { - $sql .= 'AND `configvalue` = ?'; - } - $data[] = $preCondition; - } - $affectedRows = $this->connection->executeUpdate($sql, $data); - } + $this->connection->setValues('preferences', [ + 'userid' => $userId, + 'appid' => $appName, + 'configkey' => $key, + ], [ + 'configvalue' => $value, + ], $preconditionArray); // only add to the cache if we already loaded data for the user - if ($affectedRows > 0 && isset($this->userCache[$userId])) { + if (isset($this->userCache[$userId])) { if (!isset($this->userCache[$userId][$appName])) { $this->userCache[$userId][$appName] = array(); } $this->userCache[$userId][$appName][$key] = $value; } - - if ($preCondition !== null && $affectedRows === 0) { - throw new PreConditionNotMetException; - } } /** diff --git a/lib/private/appframework/db/db.php b/lib/private/appframework/db/db.php index 812649daa78..5fdc5d1066c 100644 --- a/lib/private/appframework/db/db.php +++ b/lib/private/appframework/db/db.php @@ -147,6 +147,21 @@ class Db implements IDb { } /** + * Insert or update a row value + * + * @param string $table + * @param array $keys (column name => value) + * @param array $values (column name => value) + * @param array $updatePreconditionValues ensure values match preconditions (column name => value) + * @return int number of new rows + * @throws \Doctrine\DBAL\DBALException + * @throws PreconditionNotMetException + */ + public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) { + return $this->connection->setValues($table, $keys, $values, $updatePreconditionValues); + } + + /** * Start a transaction */ public function beginTransaction() { diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index 8fc52141d5b..a9614262603 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -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(); }); diff --git a/lib/private/db/connection.php b/lib/private/db/connection.php index 28bf3b6e05b..6c4f518dfb5 100644 --- a/lib/private/db/connection.php +++ b/lib/private/db/connection.php @@ -32,6 +32,7 @@ use Doctrine\Common\EventManager; use OC\DB\QueryBuilder\ExpressionBuilder; use OC\DB\QueryBuilder\QueryBuilder; use OCP\IDBConnection; +use OCP\PreconditionNotMetException; class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { /** @@ -241,6 +242,64 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { return $this->adapter->insertIfNotExist($table, $input, $compare); } + private function getType($value) { + if (is_bool($value)) { + return \PDO::PARAM_BOOL; + } else if (is_int($value)) { + return \PDO::PARAM_INT; + } else { + return \PDO::PARAM_STR; + } + } + + /** + * Insert or update a row value + * + * @param string $table + * @param array $keys (column name => value) + * @param array $values (column name => value) + * @param array $updatePreconditionValues ensure values match preconditions (column name => value) + * @return int number of new rows + * @throws \Doctrine\DBAL\DBALException + * @throws PreconditionNotMetException + */ + public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) { + try { + $insertQb = $this->getQueryBuilder(); + $insertQb->insert($table) + ->values( + array_map(function($value) use ($insertQb) { + return $insertQb->createNamedParameter($value, $this->getType($value)); + }, array_merge($keys, $values)) + ); + return $insertQb->execute(); + } catch (\Doctrine\DBAL\Exception\ConstraintViolationException $e) { + // value already exists, try update + $updateQb = $this->getQueryBuilder(); + $updateQb->update($table); + foreach ($values as $name => $value) { + $updateQb->set($name, $updateQb->createNamedParameter($value), $this->getType($value)); + } + $where = $updateQb->expr()->andx(); + $whereValues = array_merge($keys, $updatePreconditionValues); + foreach ($whereValues as $name => $value) { + $where->add($updateQb->expr()->eq( + $name, + $updateQb->createNamedParameter($value, $this->getType($value)), + $this->getType($value) + )); + } + $updateQb->where($where); + $affected = $updateQb->execute(); + + if ($affected === 0) { + throw new PreconditionNotMetException(); + } + + return 0; + } + } + /** * returns the error code and message as a string for logging * works with DoctrineException diff --git a/lib/private/db/querybuilder/expressionbuilder.php b/lib/private/db/querybuilder/expressionbuilder.php index de10f69b361..1e86db5a081 100644 --- a/lib/private/db/querybuilder/expressionbuilder.php +++ b/lib/private/db/querybuilder/expressionbuilder.php @@ -27,10 +27,10 @@ use OCP\IDBConnection; class ExpressionBuilder implements IExpressionBuilder { /** @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder */ - private $expressionBuilder; + protected $expressionBuilder; /** @var QuoteHelper */ - private $helper; + protected $helper; /** * Initializes a new <tt>ExpressionBuilder</tt>. @@ -109,10 +109,12 @@ class ExpressionBuilder implements IExpressionBuilder { * * @param mixed $x The left expression. * @param mixed $y The right expression. + * @param int|null $type one of the \PDO::PARAM_* constants + * required when comparing text fields for oci compatibility * * @return string */ - public function eq($x, $y) { + public function eq($x, $y, $type = null) { $x = $this->helper->quoteColumnName($x); $y = $this->helper->quoteColumnName($y); return $this->expressionBuilder->eq($x, $y); diff --git a/lib/private/db/querybuilder/ociexpressionbuilder.php b/lib/private/db/querybuilder/ociexpressionbuilder.php new file mode 100644 index 00000000000..742611bf719 --- /dev/null +++ b/lib/private/db/querybuilder/ociexpressionbuilder.php @@ -0,0 +1,33 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2016, 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\DB\QueryBuilder; + +class OCIExpressionBuilder extends ExpressionBuilder { + public function eq($x, $y, $type = null) { + $x = $this->helper->quoteColumnName($x); + $y = $this->helper->quoteColumnName($y); + if ($type === \PDO::PARAM_STR) { + $x = new QueryFunction('to_char(' . $x . ')'); + } + return $this->expressionBuilder->eq($x, $y); + } +} diff --git a/lib/private/db/querybuilder/querybuilder.php b/lib/private/db/querybuilder/querybuilder.php index 492e9bc9abf..42b290b90e7 100644 --- a/lib/private/db/querybuilder/querybuilder.php +++ b/lib/private/db/querybuilder/querybuilder.php @@ -21,6 +21,7 @@ namespace OC\DB\QueryBuilder; +use OC\DB\OracleConnection; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryFunction; use OCP\DB\QueryBuilder\IParameter; @@ -82,7 +83,11 @@ class QueryBuilder implements IQueryBuilder { * @return \OCP\DB\QueryBuilder\IExpressionBuilder */ public function expr() { - return new ExpressionBuilder($this->connection); + if ($this->connection instanceof OracleConnection) { + return new OCIExpressionBuilder($this->connection); + } else { + return new ExpressionBuilder($this->connection); + } } /** diff --git a/lib/private/integritycheck/checker.php b/lib/private/integritycheck/checker.php index 0cd01df7fe1..c256fe66d32 100644 --- a/lib/private/integritycheck/checker.php +++ b/lib/private/integritycheck/checker.php @@ -114,6 +114,7 @@ class Checker { * * @param string $folderToIterate * @return \RecursiveIteratorIterator + * @throws \Exception */ private function getFolderIterator($folderToIterate) { $dirItr = new \RecursiveDirectoryIterator( @@ -149,16 +150,33 @@ class Checker { } $relativeFileName = substr($filename, $baseDirectoryLength); + $relativeFileName = ltrim($relativeFileName, '/'); // Exclude signature.json files in the appinfo and root folder - if($relativeFileName === '/appinfo/signature.json') { + if($relativeFileName === 'appinfo/signature.json') { continue; } // Exclude signature.json files in the appinfo and core folder - if($relativeFileName === '/core/signature.json') { + if($relativeFileName === 'core/signature.json') { continue; } + // The .htaccess file in the root folder of ownCloud can contain + // custom content after the installation due to the fact that dynamic + // content is written into it at installation time as well. This + // includes for example the 404 and 403 instructions. + // Thus we ignore everything below the first occurrence of + // "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####" and have the + // hash generated based on this. + if($filename === $this->environmentHelper->getServerRoot() . '/.htaccess') { + $fileContent = file_get_contents($filename); + $explodedArray = explode('#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####', $fileContent); + if(count($explodedArray) === 2) { + $hashes[$relativeFileName] = hash('sha512', $explodedArray[0]); + continue; + } + } + $hashes[$relativeFileName] = hash_file('sha512', $filename); } return $hashes; @@ -189,17 +207,19 @@ class Checker { } /** - * Write the signature of the specified app + * Write the signature of the app in the specified folder * - * @param string $appId + * @param string $path * @param X509 $certificate * @param RSA $privateKey * @throws \Exception */ - public function writeAppSignature($appId, + public function writeAppSignature($path, X509 $certificate, RSA $privateKey) { - $path = $this->appLocator->getAppPath($appId); + if(!is_dir($path)) { + throw new \Exception('Directory does not exist.'); + } $iterator = $this->getFolderIterator($path); $hashes = $this->generateHashes($iterator, $path); $signature = $this->createSignatureData($hashes, $certificate, $privateKey); diff --git a/lib/private/integritycheck/helpers/environmenthelper.php b/lib/private/integritycheck/helpers/environmenthelper.php index 8bddcb3d794..f56f07486c2 100644 --- a/lib/private/integritycheck/helpers/environmenthelper.php +++ b/lib/private/integritycheck/helpers/environmenthelper.php @@ -34,7 +34,7 @@ class EnvironmentHelper { * @return string */ public function getServerRoot() { - return \OC::$SERVERROOT; + return rtrim(\OC::$SERVERROOT, '/'); } /** diff --git a/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php index efe7c114d9e..c3994197fc6 100644 --- a/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php +++ b/lib/private/integritycheck/iterator/excludefoldersbypathfilteriterator.php @@ -35,6 +35,7 @@ class ExcludeFoldersByPathFilterIterator extends \RecursiveFilterIterator { $this->excludedFolders = array_merge([ rtrim(\OC::$server->getConfig()->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data'), '/'), rtrim(\OC::$SERVERROOT.'/themes', '/'), + rtrim(\OC::$SERVERROOT.'/config', '/'), ], $appFolders); } diff --git a/lib/private/security/credentialsmanager.php b/lib/private/security/credentialsmanager.php new file mode 100644 index 00000000000..405922847be --- /dev/null +++ b/lib/private/security/credentialsmanager.php @@ -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(); + } + +} diff --git a/lib/private/server.php b/lib/private/server.php index 69f403308b3..2497c056600 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -65,6 +65,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; @@ -345,6 +346,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(); @@ -940,6 +944,15 @@ class Server extends ServerContainer implements IServerContainer { } /** + * 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 diff --git a/lib/private/setup.php b/lib/private/setup.php index 7903b94ccde..a96dade0665 100644 --- a/lib/private/setup.php +++ b/lib/private/setup.php @@ -408,7 +408,7 @@ class Setup { \OC::$server->getSecureRandom()); $htaccessContent = file_get_contents($setupHelper->pathToHtaccess()); - $content = ''; + $content = "#### DO NOT CHANGE ANYTHING ABOVE THIS LINE ####\n"; if (strpos($htaccessContent, 'ErrorDocument 403') === false) { //custom 403 error page $content.= "\nErrorDocument 403 ".\OC::$WEBROOT."/core/templates/403.php"; diff --git a/lib/public/db/querybuilder/iexpressionbuilder.php b/lib/public/db/querybuilder/iexpressionbuilder.php index ae62694fcaf..0549d3f0125 100644 --- a/lib/public/db/querybuilder/iexpressionbuilder.php +++ b/lib/public/db/querybuilder/iexpressionbuilder.php @@ -84,11 +84,13 @@ interface IExpressionBuilder { * * @param mixed $x The left expression. * @param mixed $y The right expression. + * @param int|null $type @since 9.0.0 one of the \PDO::PARAM_* constants + * required when comparing text fields for oci compatibility. * * @return string * @since 8.2.0 */ - public function eq($x, $y); + public function eq($x, $y, $type = null); /** * Creates a non equality comparison expression with the given arguments. diff --git a/lib/public/idbconnection.php b/lib/public/idbconnection.php index 49856fb78a5..c5767e65a82 100644 --- a/lib/public/idbconnection.php +++ b/lib/public/idbconnection.php @@ -109,6 +109,20 @@ interface IDBConnection { public function insertIfNotExist($table, $input, array $compare = null); /** + * Insert or update a row value + * + * @param string $table + * @param array $keys (column name => value) + * @param array $values (column name => value) + * @param array $updatePreconditionValues ensure values match preconditions (column name => value) + * @return int number of new rows + * @throws \Doctrine\DBAL\DBALException + * @throws PreconditionNotMetException + * @since 9.0.0 + */ + public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []); + + /** * Start a transaction * @since 6.0.0 */ diff --git a/lib/public/iservercontainer.php b/lib/public/iservercontainer.php index 1976f59f613..82084f021e8 100644 --- a/lib/public/iservercontainer.php +++ b/lib/public/iservercontainer.php @@ -181,6 +181,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 * @return \OCP\IDb diff --git a/lib/public/security/icredentialsmanager.php b/lib/public/security/icredentialsmanager.php new file mode 100644 index 00000000000..d3d076f043e --- /dev/null +++ b/lib/public/security/icredentialsmanager.php @@ -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); + +} diff --git a/tests/data/integritycheck/htaccessUnmodified/.htaccess b/tests/data/integritycheck/htaccessUnmodified/.htaccess new file mode 100644 index 00000000000..9bcb05db96c --- /dev/null +++ b/tests/data/integritycheck/htaccessUnmodified/.htaccess @@ -0,0 +1 @@ +# Start of valid file
\ No newline at end of file diff --git a/tests/data/integritycheck/htaccessUnmodified/subfolder/.htaccess b/tests/data/integritycheck/htaccessUnmodified/subfolder/.htaccess new file mode 100644 index 00000000000..33d4437c928 --- /dev/null +++ b/tests/data/integritycheck/htaccessUnmodified/subfolder/.htaccess @@ -0,0 +1,4 @@ +# Start of valid file +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### + +# Content that should change the hash in the root folder
\ No newline at end of file diff --git a/tests/data/integritycheck/htaccessWithInvalidModifiedContent/.htaccess b/tests/data/integritycheck/htaccessWithInvalidModifiedContent/.htaccess new file mode 100644 index 00000000000..d3c2f69f120 --- /dev/null +++ b/tests/data/integritycheck/htaccessWithInvalidModifiedContent/.htaccess @@ -0,0 +1,5 @@ +# Start of valid file +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### +# Content that should not modify the hash +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### +# but it does because it is twice
\ No newline at end of file diff --git a/tests/data/integritycheck/htaccessWithValidModifiedContent/.htaccess b/tests/data/integritycheck/htaccessWithValidModifiedContent/.htaccess new file mode 100644 index 00000000000..33d4437c928 --- /dev/null +++ b/tests/data/integritycheck/htaccessWithValidModifiedContent/.htaccess @@ -0,0 +1,4 @@ +# Start of valid file +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### + +# Content that should change the hash in the root folder
\ No newline at end of file diff --git a/tests/data/integritycheck/htaccessWithValidModifiedContent/subfolder/.htaccess b/tests/data/integritycheck/htaccessWithValidModifiedContent/subfolder/.htaccess new file mode 100644 index 00000000000..33d4437c928 --- /dev/null +++ b/tests/data/integritycheck/htaccessWithValidModifiedContent/subfolder/.htaccess @@ -0,0 +1,4 @@ +# Start of valid file +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### + +# Content that should change the hash in the root folder
\ No newline at end of file diff --git a/tests/lib/allconfig.php b/tests/lib/allconfig.php index 0caf8163cfc..869bf9b8d66 100644 --- a/tests/lib/allconfig.php +++ b/tests/lib/allconfig.php @@ -90,16 +90,7 @@ class TestAllConfig extends \Test\TestCase { } public function testSetUserValueWithPreCondition() { - // mock the check for the database to run the correct SQL statements for each database type - $systemConfig = $this->getMockBuilder('\OC\SystemConfig') - ->disableOriginalConstructor() - ->getMock(); - $systemConfig->expects($this->once()) - ->method('getValue') - ->with($this->equalTo('dbtype'), - $this->equalTo('sqlite')) - ->will($this->returnValue(\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite'))); - $config = $this->getConfig($systemConfig); + $config = $this->getConfig(); $selectAllSQL = 'SELECT `userid`, `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?'; @@ -136,16 +127,7 @@ class TestAllConfig extends \Test\TestCase { * @expectedException \OCP\PreConditionNotMetException */ public function testSetUserValueWithPreConditionFailure() { - // mock the check for the database to run the correct SQL statements for each database type - $systemConfig = $this->getMockBuilder('\OC\SystemConfig') - ->disableOriginalConstructor() - ->getMock(); - $systemConfig->expects($this->once()) - ->method('getValue') - ->with($this->equalTo('dbtype'), - $this->equalTo('sqlite')) - ->will($this->returnValue(\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite'))); - $config = $this->getConfig($systemConfig); + $config = $this->getConfig(); $selectAllSQL = 'SELECT `userid`, `appid`, `configkey`, `configvalue` FROM `*PREFIX*preferences` WHERE `userid` = ?'; diff --git a/tests/lib/command/integrity/SignAppTest.php b/tests/lib/command/integrity/SignAppTest.php index b7c34585c5c..44a644c45df 100644 --- a/tests/lib/command/integrity/SignAppTest.php +++ b/tests/lib/command/integrity/SignAppTest.php @@ -23,6 +23,7 @@ namespace Test\Command\Integrity; use OC\Core\Command\Integrity\SignApp; use OC\IntegrityCheck\Checker; use OC\IntegrityCheck\Helpers\FileAccessHelper; +use OCP\IURLGenerator; use Test\TestCase; class SignAppTest extends TestCase { @@ -32,6 +33,8 @@ class SignAppTest extends TestCase { private $signApp; /** @var FileAccessHelper */ private $fileAccessHelper; + /** @var IURLGenerator */ + private $urlGenerator; public function setUp() { parent::setUp(); @@ -39,20 +42,23 @@ class SignAppTest extends TestCase { ->disableOriginalConstructor()->getMock(); $this->fileAccessHelper = $this->getMockBuilder('\OC\IntegrityCheck\Helpers\FileAccessHelper') ->disableOriginalConstructor()->getMock(); + $this->urlGenerator = $this->getMockBuilder('\OCP\IURLGenerator') + ->disableOriginalConstructor()->getMock(); $this->signApp = new SignApp( $this->checker, - $this->fileAccessHelper + $this->fileAccessHelper, + $this->urlGenerator ); } - public function testExecuteWithMissingAppId() { + public function testExecuteWithMissingPath() { $inputInterface = $this->getMock('\Symfony\Component\Console\Input\InputInterface'); $outputInterface = $this->getMock('\Symfony\Component\Console\Output\OutputInterface'); $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue(null)); $inputInterface ->expects($this->at(1)) @@ -68,7 +74,7 @@ class SignAppTest extends TestCase { $outputInterface ->expects($this->at(0)) ->method('writeln') - ->with('--appId, --privateKey and --certificate are required.'); + ->with('This command requires the --path, --privateKey and --certificate.'); $this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]); } @@ -80,7 +86,7 @@ class SignAppTest extends TestCase { $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue('AppId')); $inputInterface ->expects($this->at(1)) @@ -94,9 +100,9 @@ class SignAppTest extends TestCase { ->will($this->returnValue('Certificate')); $outputInterface - ->expects($this->at(0)) - ->method('writeln') - ->with('--appId, --privateKey and --certificate are required.'); + ->expects($this->at(0)) + ->method('writeln') + ->with('This command requires the --path, --privateKey and --certificate.'); $this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]); } @@ -108,7 +114,7 @@ class SignAppTest extends TestCase { $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue('AppId')); $inputInterface ->expects($this->at(1)) @@ -124,7 +130,7 @@ class SignAppTest extends TestCase { $outputInterface ->expects($this->at(0)) ->method('writeln') - ->with('--appId, --privateKey and --certificate are required.'); + ->with('This command requires the --path, --privateKey and --certificate.'); $this->invokePrivate($this->signApp, 'execute', [$inputInterface, $outputInterface]); } @@ -136,7 +142,7 @@ class SignAppTest extends TestCase { $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue('AppId')); $inputInterface ->expects($this->at(1)) @@ -170,7 +176,7 @@ class SignAppTest extends TestCase { $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue('AppId')); $inputInterface ->expects($this->at(1)) @@ -209,7 +215,7 @@ class SignAppTest extends TestCase { $inputInterface ->expects($this->at(0)) ->method('getOption') - ->with('appId') + ->with('path') ->will($this->returnValue('AppId')); $inputInterface ->expects($this->at(1)) diff --git a/tests/lib/db/connection.php b/tests/lib/db/connection.php index 348a5e31e09..dd9b31f3ed7 100644 --- a/tests/lib/db/connection.php +++ b/tests/lib/db/connection.php @@ -25,20 +25,17 @@ class Connection extends \Test\TestCase { */ private $connection; - public static function setUpBeforeClass() - { + public static function setUpBeforeClass() { self::dropTestTable(); parent::setUpBeforeClass(); } - public static function tearDownAfterClass() - { + public static function tearDownAfterClass() { self::dropTestTable(); parent::tearDownAfterClass(); } - protected static function dropTestTable() - { + protected static function dropTestTable() { if (\OC::$server->getConfig()->getSystemValue('dbtype', 'sqlite') !== 'oci') { \OC::$server->getDatabaseConnection()->dropTable('table'); } @@ -92,4 +89,93 @@ class Connection extends \Test\TestCase { $this->connection->dropTable('table'); $this->assertTableNotExist('table'); } + + private function getTextValueByIntergerField($integerField) { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select('textfield') + ->from('table') + ->where($builder->expr()->eq('integerfield', $builder->createNamedParameter($integerField, \PDO::PARAM_INT))); + + $result = $query->execute(); + return $result->fetchColumn(); + } + + public function testSetValues() { + $this->makeTestTable(); + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'foo', + 'clobfield' => 'not_null' + ]); + + $this->assertEquals('foo', $this->getTextValueByIntergerField(1)); + + $this->connection->dropTable('table'); + } + + public function testSetValuesOverWrite() { + $this->makeTestTable(); + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'foo', + 'clobfield' => 'not_null' + ]); + + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'bar' + ]); + + $this->assertEquals('bar', $this->getTextValueByIntergerField(1)); + + $this->connection->dropTable('table'); + } + + public function testSetValuesOverWritePrecondition() { + $this->makeTestTable(); + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'foo', + 'booleanfield' => true, + 'clobfield' => 'not_null' + ]); + + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'bar' + ], [ + 'booleanfield' => true + ]); + + $this->assertEquals('bar', $this->getTextValueByIntergerField(1)); + + $this->connection->dropTable('table'); + } + + /** + * @expectedException \OCP\PreConditionNotMetException + */ + public function testSetValuesOverWritePreconditionFailed() { + $this->makeTestTable(); + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'foo', + 'booleanfield' => true, + 'clobfield' => 'not_null' + ]); + + $this->connection->setValues('table', [ + 'integerfield' => 1 + ], [ + 'textfield' => 'bar' + ], [ + 'booleanfield' => false + ]); + } } diff --git a/tests/lib/integritycheck/checkertest.php b/tests/lib/integritycheck/checkertest.php index eee9299e6af..dec3ea84a64 100644 --- a/tests/lib/integritycheck/checkertest.php +++ b/tests/lib/integritycheck/checkertest.php @@ -77,7 +77,7 @@ class CheckerTest extends TestCase { /** * @expectedException \Exception - * @expectedExceptionMessage Directory name must not be empty. + * @expectedExceptionMessage Directory does not exist. */ public function testWriteAppSignatureOfNotExistingApp() { $keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/SomeApp.crt'); @@ -98,11 +98,6 @@ class CheckerTest extends TestCase { "signature": "Y5yvXvcGHVPuRRatKVDUONWq1FpLXugZd6Km\/+aEHsQj7coVl9FeMj9OsWamBf7yRIw3dtNLguTLlAA9QAv\/b0uHN3JnbNZN+dwFOve4NMtqXfSDlWftqKN00VS+RJXpG1S2IIx9Poyp2NoghL\/5AuTv4GHiNb7zU\/DT\/kt71pUGPgPR6IIFaE+zHOD96vjYkrH+GfWZzKR0FCdLib9yyNvk+EGrcjKM6qjs2GKfS\/XFjj\/\/neDnh\/0kcPuKE3ZbofnI4TIDTv0CGqvOp7PtqVNc3Vy\/UKa7uF1PT0MAUKMww6EiMUSFZdUVP4WWF0Y72W53Qdtf1hrAZa2kfKyoK5kd7sQmCSKUPSU8978AUVZlBtTRlyT803IKwMV0iHMkw+xYB1sN2FlHup\/DESADqxhdgYuK35bCPvgkb4SBe4B8Voz\/izTvcP7VT5UvkYdAO+05\/jzdaHEmzmsD92CFfvX0q8O\/Y\/29ubftUJsqcHeMDKgcR4eZOE8+\/QVc\/89QO6WnKNuNuV+5bybO6g6PAdC9ZPsCvnihS61O2mwRXHLR3jv2UleFWm+lZEquPKtkhi6SLtDiijA4GV6dmS+dzujSLb7hGeD5o1plZcZ94uhWljl+QIp82+zU\/lYB1Zfr4Mb4e+V7r2gv7Fbv7y6YtjE2GIQwRhC5jq56bD0ZB+I=", "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEwTCCAqmgAwIBAgIUWv0iujufs5lUr0svCf\/qTQvoyKAwDQYJKoZIhvcNAQEF\r\nBQAwIzEhMB8GA1UECgwYb3duQ2xvdWQgQ29kZSBTaWduaW5nIENBMB4XDTE1MTEw\r\nMzIyNDk1M1oXDTE2MTEwMzIyNDk1M1owEjEQMA4GA1UEAwwHU29tZUFwcDCCAiIw\r\nDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK8q0x62agGSRBqeWsaeEwFfepMk\r\nF8cAobMMi50qHCv9IrOn\/ZH9l52xBrbIkErVmRjmly0d4JhD8Ymhidsh9ONKYl\/j\r\n+ishsZDM8eNNdp3Ew+fEYVvY1W7mR1qU24NWj0bzVsClI7hvPVIuw7AjfBDq1C5+\r\nA+ZSLSXYvOK2cEWjdxQfuNZwEZSjmA63DUllBIrm35IaTvfuyhU6BW9yHZxmb8+M\r\nw0xDv30D5UkE\/2N7Pa\/HQJLxCR+3zKibRK3nUyRDLSXxMkU9PnFNaPNX59VPgyj4\r\nGB1CFSToldJVPF4pzh7p36uGXZVxs8m3LFD4Ol8mhi7jkxDZjqFN46gzR0r23Py6\r\ndol9vfawGIoUwp9LvL0S7MvdRY0oazLXwClLP4OQ17zpSMAiCj7fgNT661JamPGj\r\nt5O7Zn2wA7I4ddDS\/HDTWCu98Zwc9fHIpsJPgCZ9awoqxi4Mnf7Pk9g5nnXhszGC\r\ncxxIASQKM+GhdzoRxKknax2RzUCwCzcPRtCj8AQT\/x\/mqN3PfRmlnFBNACUw9bpZ\r\nSOoNq2pCF9igftDWpSIXQ38pVpKLWowjjg3DVRmVKBgivHnUnVLyzYBahHPj0vaz\r\ntFtUFRaqXDnt+4qyUGyrT5h5pjZaTcHIcSB4PiarYwdVvgslgwnQzOUcGAzRWBD4\r\n6jV2brP5vFY3g6iPAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBACTY3CCHC+Z28gCf\r\nFWGKQ3wAKs+k4+0yoti0qm2EKX7rSGQ0PHSas6uW79WstC4Rj+DYkDtIhGMSg8FS\r\nHVGZHGBCc0HwdX+BOAt3zi4p7Sf3oQef70\/4imPoKxbAVCpd\/cveVcFyDC19j1yB\r\nBapwu87oh+muoeaZxOlqQI4UxjBlR\/uRSMhOn2UGauIr3dWJgAF4pGt7TtIzt+1v\r\n0uA6FtN1Y4R5O8AaJPh1bIG0CVvFBE58esGzjEYLhOydgKFnEP94kVPgJD5ds9C3\r\npPhEpo1dRpiXaF7WGIV1X6DI\/ipWvfrF7CEy6I\/kP1InY\/vMDjQjeDnJ\/VrXIWXO\r\nyZvHXVaN\/m+1RlETsH7YO\/QmxRue9ZHN3gvvWtmpCeA95sfpepOk7UcHxHZYyQbF\r\n49\/au8j+5tsr4A83xzsT1JbcKRxkAaQ7WDJpOnE5O1+H0fB+BaLakTg6XX9d4Fo7\r\n7Gin7hVWX7pL+JIyxMzME3LhfI61+CRcqZQIrpyaafUziPQbWIPfEs7h8tCOWyvW\r\nUO8ZLervYCB3j44ivkrxPlcBklDCqqKKBzDP9dYOtS\/P4RB1NkHA9+NTvmBpTonS\r\nSFXdg9fFMD7VfjDE3Vnk+8DWkVH5wBYowTAD7w9Wuzr7DumiAULexnP\/Y7xwxLv7\r\n4B+pXTAcRK0zECDEaX3npS8xWzrB\r\n-----END CERTIFICATE-----" }'; - $this->appLocator - ->expects($this->once()) - ->method('getAppPath') - ->with('SomeExistingApp') - ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/app/')); $this->fileAccessHelper ->expects($this->once()) ->method('file_put_contents') @@ -117,7 +112,7 @@ class CheckerTest extends TestCase { $rsa->loadKey($rsaPrivateKey); $x509 = new X509(); $x509->loadX509($keyBundle); - $this->checker->writeAppSignature('SomeExistingApp', $x509, $rsa); + $this->checker->writeAppSignature(\OC::$SERVERROOT . '/tests/data/integritycheck/app/', $x509, $rsa); } public function testVerifyAppSignatureWithoutSignatureData() { @@ -473,6 +468,95 @@ class CheckerTest extends TestCase { $this->checker->writeCoreSignature($x509, $rsa); } + public function testWriteCoreSignatureWithUnmodifiedHtaccess() { + $expectedSignatureFileData = '{ + "hashes": { + ".htaccess": "dc479770a6232061e04a768ee1f9133fdb3aea7b3a99f7105b0e0b6197474733e8d14b5b2bbad054e6b62a410fe5d0b3d790242dee1e0f11274af2100f5289e2", + "subfolder\/.htaccess": "2c57b1e25050e11dc3ae975832f378c452159f7b69f818e47eeeafadd6ba568517461dcb4d843b90b906cd7c89d161bc1b89dff8e3ae0eb6f5088508c47befd1" + }, + "signature": "nRtR377DB\/I\/4hmh9q3elMQYfSHnQFlNtjchNgrdfmUQqVmgkU\/4qgGyxDqYkV8mSMbH2gYysfP42nx\/3zSo7n0dBYDfU87Q6f96Cv597vEV27do8CaBkEk8Xjn2SxhHw8hVxracvE2OBAPxk0H3sRp\/cQBgjoXpju4kQin0N5E+DEJMh7Sp+u8aKoFpb+2FaAZJFn\/hnqxLTlVi2nyDxGL3U0eobWY+jWH9XPt52v3Hyh8TDhcAnQ1cN30B8Jn2+jkrm8ib+buchaCXHk0cPX72xuPECdwOEKLCBNrJa3FGSvO1zWiecnCgxCXgt+R8hUgsVPTsbrdFY2YRJGIhHndYZL98XzgG7cw85SnnMMe2SulzeL7xANGF8qiEVyiC7x83bbj5xOkeM\/CUTajrLBO3vyZ23KKOxvskjgI0t+Zw1zFsl+sYW0\/O\/V5WzPOwMwV8+iApQ8k9gEMiYQg98QLEMYnSohncmp0Z9qx2qFcQuHLcKJVa1J6wGtE\/EHR\/4d0aYPd6IRjg+qshCJmdzud\/12xjpGTl+BT0Hi0VsU5o7ZMi7WhmukZmmv8u0uZsvKREQNATm4cO4WCkYySt5O9gZEJOF+jjgeynDoAh09lyrNXIgMpM9ufm\/XEG\/I\/f2zIwbAUc6J6qks5OuYlJzW5vscTiOKhwcGZU9WBLgh0=", + "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEvjCCAqagAwIBAgIUc\/0FxYrsgSs9rDxp03EJmbjN0NwwDQYJKoZIhvcNAQEF\r\nBQAwIzEhMB8GA1UECgwYb3duQ2xvdWQgQ29kZSBTaWduaW5nIENBMB4XDTE1MTEw\r\nMzIxMDMzM1oXDTE2MTEwMzIxMDMzM1owDzENMAsGA1UEAwwEY29yZTCCAiIwDQYJ\r\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBALb6EgHpkAqZbO5vRO8XSh7G7XGWHw5s\r\niOf4RwPXR6SE9bWZEm\/b72SfWk\/\/J6AbrD8WiOzBuT\/ODy6k5T1arEdHO+Pux0W1\r\nMxYJJI4kH74KKgMpC0SB0Rt+8WrMqV1r3hhJ46df6Xr\/xolP3oD+eLbShPcblhdS\r\nVtkZEkoev8Sh6L2wDCeHDyPxzvj1w2dTdGVO9Kztn0xIlyfEBakqvBWtcxyi3Ln0\r\nklnxlMx3tPDUE4kqvpia9qNiB1AN2PV93eNr5\/2riAzIssMFSCarWCx0AKYb54+d\r\nxLpcYFyqPJ0ydBCkF78DD45RCZet6PNYkdzgbqlUWEGGomkuDoJbBg4wzgzO0D77\r\nH87KFhYW8tKFFvF1V3AHl\/sFQ9tDHaxM9Y0pZ2jPp\/ccdiqnmdkBxBDqsiRvHvVB\r\nCn6qpb4vWGFC7vHOBfYspmEL1zLlKXZv3ezMZEZw7O9ZvUP3VO\/wAtd2vUW8UFiq\r\ns2v1QnNLN6jNh51obcwmrBvWhJy9vQIdtIjQbDxqWTHh1zUSrw9wrlklCBZ\/zrM0\r\ni8nfCFwTxWRxp3H9KoECzO\/zS5R5KIS7s3\/wq\/w9T2Ie4rcecgXwDizwnn0C\/aKc\r\nbDIjujpL1s9HO05pcD\/V3wKcPZ1izymBkmMyIbL52iRVN5FTVHeZdXPpFuq+CTQJ\r\nQ238lC+A\/KOVAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAGoKTnh8RfJV4sQItVC2\r\nAvfJagkrIqZ3iiQTUBQGTKBsTnAqE1H7QgUSV9vSd+8rgvHkyZsRjmtyR1e3A6Ji\r\noNCXUbExC\/0iCPUqdHZIVb+Lc\/vWuv4ByFMybGPydgtLoEUX2ZrKFWmcgZFDUSRd\r\n9Uj26vtUhCC4bU4jgu6hIrR9IuxOBLQUxGTRZyAcXvj7obqRAEZwFAKQgFpfpqTb\r\nH+kjcbZSaAlLVSF7vBc1syyI8RGYbqpwvtREqJtl5IEIwe6huEqJ3zPnlP2th\/55\r\ncf3Fovj6JJgbb9XFxrdnsOsDOu\/tpnaRWlvv5ib4+SzG5wWFT5UUEo4Wg2STQiiX\r\nuVSRQxK1LE1yg84bs3NZk9FSQh4B8vZVuRr5FaJsZZkwlFlhRO\/\/+TJtXRbyNgsf\r\noMRZGi8DLGU2SGEAHcRH\/QZHq\/XDUWVzdxrSBYcy7GSpT7UDVzGv1rEJUrn5veP1\r\n0KmauAqtiIaYRm4f6YBsn0INcZxzIPZ0p8qFtVZBPeHhvQtvOt0iXI\/XUxEWOa2F\r\nK2EqhErgMK\/N07U1JJJay5tYZRtvkGq46oP\/5kQG8hYST0MDK6VihJoPpvCmAm4E\r\npEYKQ96x6A4EH9Y9mZlYozH\/eqmxPbTK8n89\/p7Ydun4rI+B2iiLnY8REWWy6+UQ\r\nV204fGUkJqW5CrKy3P3XvY9X\r\n-----END CERTIFICATE-----" +}'; + $this->environmentHelper + ->expects($this->any()) + ->method('getServerRoot') + ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/htaccessUnmodified/')); + $this->fileAccessHelper + ->expects($this->once()) + ->method('file_put_contents') + ->with( + \OC::$SERVERROOT . '/tests/data/integritycheck/htaccessUnmodified//core/signature.json', + $expectedSignatureFileData + ); + + $keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/core.crt'); + $rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/core.key'); + $rsa = new RSA(); + $rsa->loadKey($rsaPrivateKey); + $x509 = new X509(); + $x509->loadX509($keyBundle); + $this->checker->writeCoreSignature($x509, $rsa); + } + + public function testWriteCoreSignatureWithInvalidModifiedHtaccess() { + $expectedSignatureFileData = '{ + "hashes": { + ".htaccess": "4a54273dc8d697b2ca615acf2ae2c1ee3c1c643492cb04f42b10984fa9aacff1420dc829fd82f93ad3476fbd0cdab0251142c887dc8f872d03e39a3a3eb6d381" + }, + "signature": "qpDddYGgAKNR3TszOgjPXRphUl2P9Ym5OQaetltocgZASGDkOun5D64+1D0QJRKb4SG2+48muxGOHyL2Ngos4NUrrSR+SIkywZacay82YQBCEdr7\/4MjW1WHRPjvboLwEJwViw0EdAjsWRpD68aPnzUGrGsy2BsCo06P5iwjk9cXcHxdjC9R39npvoC3QNvQ2jmNIbh1Lc4U97dbb+CsXEQCLU1OSa9p3q6cEFV98Easwt7uF\/DzHK+CbeZlxVZ0DwLh2\/ylT1PyGou8QC1b3vKAnPjLWMO+UsCPpCKhk3C5pV+5etQ8puGd+0x2t5tEU+qXxLzek91zWNC+rqgC\/WlqLKbwPb\/BCHs4zLGV55Q2fEQmT21x0KCUELdPs4dBnYP4Ox5tEDugtJujWFzOHzoY6gGa\/BY\/78pSZXmq9o8dWkBEtioWWvaNZ1rM0ddE83GBlBTgjigi9Ay1D++bUW\/FCBB7CMk6qyNlV81H+cBuIEODw2aymmkM9LLDD2Qbmvo8gHEPRjiQxPC5OpDlcdSNiL+zcxVxeuX4FpT+9xzz\/\/DRONhufxRpsbuCOMxd96RW7y9U2N2Uxb3Bzn\/BIqEayUUsdgZjfaGcXXYKR+chu\/LOwNYN6RlnLsgqL\/dhGKwlRVKXw1RA2\/af\/CpqyR7uVP6al1YJo\/YJ+5XJ6zE=", + "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEvjCCAqagAwIBAgIUc\/0FxYrsgSs9rDxp03EJmbjN0NwwDQYJKoZIhvcNAQEF\r\nBQAwIzEhMB8GA1UECgwYb3duQ2xvdWQgQ29kZSBTaWduaW5nIENBMB4XDTE1MTEw\r\nMzIxMDMzM1oXDTE2MTEwMzIxMDMzM1owDzENMAsGA1UEAwwEY29yZTCCAiIwDQYJ\r\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBALb6EgHpkAqZbO5vRO8XSh7G7XGWHw5s\r\niOf4RwPXR6SE9bWZEm\/b72SfWk\/\/J6AbrD8WiOzBuT\/ODy6k5T1arEdHO+Pux0W1\r\nMxYJJI4kH74KKgMpC0SB0Rt+8WrMqV1r3hhJ46df6Xr\/xolP3oD+eLbShPcblhdS\r\nVtkZEkoev8Sh6L2wDCeHDyPxzvj1w2dTdGVO9Kztn0xIlyfEBakqvBWtcxyi3Ln0\r\nklnxlMx3tPDUE4kqvpia9qNiB1AN2PV93eNr5\/2riAzIssMFSCarWCx0AKYb54+d\r\nxLpcYFyqPJ0ydBCkF78DD45RCZet6PNYkdzgbqlUWEGGomkuDoJbBg4wzgzO0D77\r\nH87KFhYW8tKFFvF1V3AHl\/sFQ9tDHaxM9Y0pZ2jPp\/ccdiqnmdkBxBDqsiRvHvVB\r\nCn6qpb4vWGFC7vHOBfYspmEL1zLlKXZv3ezMZEZw7O9ZvUP3VO\/wAtd2vUW8UFiq\r\ns2v1QnNLN6jNh51obcwmrBvWhJy9vQIdtIjQbDxqWTHh1zUSrw9wrlklCBZ\/zrM0\r\ni8nfCFwTxWRxp3H9KoECzO\/zS5R5KIS7s3\/wq\/w9T2Ie4rcecgXwDizwnn0C\/aKc\r\nbDIjujpL1s9HO05pcD\/V3wKcPZ1izymBkmMyIbL52iRVN5FTVHeZdXPpFuq+CTQJ\r\nQ238lC+A\/KOVAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAGoKTnh8RfJV4sQItVC2\r\nAvfJagkrIqZ3iiQTUBQGTKBsTnAqE1H7QgUSV9vSd+8rgvHkyZsRjmtyR1e3A6Ji\r\noNCXUbExC\/0iCPUqdHZIVb+Lc\/vWuv4ByFMybGPydgtLoEUX2ZrKFWmcgZFDUSRd\r\n9Uj26vtUhCC4bU4jgu6hIrR9IuxOBLQUxGTRZyAcXvj7obqRAEZwFAKQgFpfpqTb\r\nH+kjcbZSaAlLVSF7vBc1syyI8RGYbqpwvtREqJtl5IEIwe6huEqJ3zPnlP2th\/55\r\ncf3Fovj6JJgbb9XFxrdnsOsDOu\/tpnaRWlvv5ib4+SzG5wWFT5UUEo4Wg2STQiiX\r\nuVSRQxK1LE1yg84bs3NZk9FSQh4B8vZVuRr5FaJsZZkwlFlhRO\/\/+TJtXRbyNgsf\r\noMRZGi8DLGU2SGEAHcRH\/QZHq\/XDUWVzdxrSBYcy7GSpT7UDVzGv1rEJUrn5veP1\r\n0KmauAqtiIaYRm4f6YBsn0INcZxzIPZ0p8qFtVZBPeHhvQtvOt0iXI\/XUxEWOa2F\r\nK2EqhErgMK\/N07U1JJJay5tYZRtvkGq46oP\/5kQG8hYST0MDK6VihJoPpvCmAm4E\r\npEYKQ96x6A4EH9Y9mZlYozH\/eqmxPbTK8n89\/p7Ydun4rI+B2iiLnY8REWWy6+UQ\r\nV204fGUkJqW5CrKy3P3XvY9X\r\n-----END CERTIFICATE-----" +}'; + $this->environmentHelper + ->expects($this->any()) + ->method('getServerRoot') + ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/htaccessWithInvalidModifiedContent/')); + $this->fileAccessHelper + ->expects($this->once()) + ->method('file_put_contents') + ->with( + \OC::$SERVERROOT . '/tests/data/integritycheck/htaccessWithInvalidModifiedContent//core/signature.json', + $expectedSignatureFileData + ); + + $keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/core.crt'); + $rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/core.key'); + $rsa = new RSA(); + $rsa->loadKey($rsaPrivateKey); + $x509 = new X509(); + $x509->loadX509($keyBundle); + $this->checker->writeCoreSignature($x509, $rsa); + } + + public function testWriteCoreSignatureWithValidModifiedHtaccess() { + $expectedSignatureFileData = '{ + "hashes": { + ".htaccess": "a232e6a616c40635d0220e47ebaade40348aadf141a67a331b8870b8fae056584e52fe8b56c49468ee17b58f92cbcd269dc30ae598d6206e97f7d8bb00a766c6", + "subfolder\/.htaccess": "2c57b1e25050e11dc3ae975832f378c452159f7b69f818e47eeeafadd6ba568517461dcb4d843b90b906cd7c89d161bc1b89dff8e3ae0eb6f5088508c47befd1" + }, + "signature": "LNHvrAFg7NJL9h8TanIFmiI3xnmNRz8pltVgRJpnQTqLJCkhZWV5+poHIii\/\/dI4NhBijsoN0AAJckf1KFzyeI2rOk3w+niaOEXX7khoJDgbxuz0kwN13Bxa1A6j0cMFqm9IIWet0JK9MKaL8K\/n3CzNYovXhRBdJsYTQVWvkaY5KMQgTP2roqgaLBABfI8+fuZVnKie1D737UJ3LhxesEtqr9mJEUSdYuN1QpaScdv7bMkX7xTcg02T5Ljs4F0KsKSME43Pzxm33qCQ\/Gyfsz\/iNKHYQztg9wPkSanbqvFnDtHhcIhKBsETCbNuBZqBk0AwYCupLIJTjC6SShHc4TtWiv834wtSmc1fYfzrsq7gJalJifFAaeGemzFwkePFlVqjdYc63KSqK8ut0jEcjKPAmJ+5NCUoxc8iASMJCesf31mzUPlw1L9LCBMA0aywDqkZYK4tJHZYMvXc4UkSs19OuAzUbXMoVHsJ03ftfC02gpg4hqZDSiBqYuyKMvt2xuutTA+xQcl3fQGUuNdSmBqUFm0D5cCvT10aZPNUXA2cnS+89u58QSxO1wEZJCYKOrDvX1oqOyJs\/c8GNip3LwheIF2KB8\/Zh83h8ZncDxuesAzq89IjV815K3P1G\/kSVPhvQapw1KMLu9rBDZ3FVvQw8K8fg5a7opBrK2ggGds=", + "certificate": "-----BEGIN CERTIFICATE-----\r\nMIIEvjCCAqagAwIBAgIUc\/0FxYrsgSs9rDxp03EJmbjN0NwwDQYJKoZIhvcNAQEF\r\nBQAwIzEhMB8GA1UECgwYb3duQ2xvdWQgQ29kZSBTaWduaW5nIENBMB4XDTE1MTEw\r\nMzIxMDMzM1oXDTE2MTEwMzIxMDMzM1owDzENMAsGA1UEAwwEY29yZTCCAiIwDQYJ\r\nKoZIhvcNAQEBBQADggIPADCCAgoCggIBALb6EgHpkAqZbO5vRO8XSh7G7XGWHw5s\r\niOf4RwPXR6SE9bWZEm\/b72SfWk\/\/J6AbrD8WiOzBuT\/ODy6k5T1arEdHO+Pux0W1\r\nMxYJJI4kH74KKgMpC0SB0Rt+8WrMqV1r3hhJ46df6Xr\/xolP3oD+eLbShPcblhdS\r\nVtkZEkoev8Sh6L2wDCeHDyPxzvj1w2dTdGVO9Kztn0xIlyfEBakqvBWtcxyi3Ln0\r\nklnxlMx3tPDUE4kqvpia9qNiB1AN2PV93eNr5\/2riAzIssMFSCarWCx0AKYb54+d\r\nxLpcYFyqPJ0ydBCkF78DD45RCZet6PNYkdzgbqlUWEGGomkuDoJbBg4wzgzO0D77\r\nH87KFhYW8tKFFvF1V3AHl\/sFQ9tDHaxM9Y0pZ2jPp\/ccdiqnmdkBxBDqsiRvHvVB\r\nCn6qpb4vWGFC7vHOBfYspmEL1zLlKXZv3ezMZEZw7O9ZvUP3VO\/wAtd2vUW8UFiq\r\ns2v1QnNLN6jNh51obcwmrBvWhJy9vQIdtIjQbDxqWTHh1zUSrw9wrlklCBZ\/zrM0\r\ni8nfCFwTxWRxp3H9KoECzO\/zS5R5KIS7s3\/wq\/w9T2Ie4rcecgXwDizwnn0C\/aKc\r\nbDIjujpL1s9HO05pcD\/V3wKcPZ1izymBkmMyIbL52iRVN5FTVHeZdXPpFuq+CTQJ\r\nQ238lC+A\/KOVAgMBAAEwDQYJKoZIhvcNAQEFBQADggIBAGoKTnh8RfJV4sQItVC2\r\nAvfJagkrIqZ3iiQTUBQGTKBsTnAqE1H7QgUSV9vSd+8rgvHkyZsRjmtyR1e3A6Ji\r\noNCXUbExC\/0iCPUqdHZIVb+Lc\/vWuv4ByFMybGPydgtLoEUX2ZrKFWmcgZFDUSRd\r\n9Uj26vtUhCC4bU4jgu6hIrR9IuxOBLQUxGTRZyAcXvj7obqRAEZwFAKQgFpfpqTb\r\nH+kjcbZSaAlLVSF7vBc1syyI8RGYbqpwvtREqJtl5IEIwe6huEqJ3zPnlP2th\/55\r\ncf3Fovj6JJgbb9XFxrdnsOsDOu\/tpnaRWlvv5ib4+SzG5wWFT5UUEo4Wg2STQiiX\r\nuVSRQxK1LE1yg84bs3NZk9FSQh4B8vZVuRr5FaJsZZkwlFlhRO\/\/+TJtXRbyNgsf\r\noMRZGi8DLGU2SGEAHcRH\/QZHq\/XDUWVzdxrSBYcy7GSpT7UDVzGv1rEJUrn5veP1\r\n0KmauAqtiIaYRm4f6YBsn0INcZxzIPZ0p8qFtVZBPeHhvQtvOt0iXI\/XUxEWOa2F\r\nK2EqhErgMK\/N07U1JJJay5tYZRtvkGq46oP\/5kQG8hYST0MDK6VihJoPpvCmAm4E\r\npEYKQ96x6A4EH9Y9mZlYozH\/eqmxPbTK8n89\/p7Ydun4rI+B2iiLnY8REWWy6+UQ\r\nV204fGUkJqW5CrKy3P3XvY9X\r\n-----END CERTIFICATE-----" +}'; + $this->environmentHelper + ->expects($this->any()) + ->method('getServerRoot') + ->will($this->returnValue(\OC::$SERVERROOT . '/tests/data/integritycheck/htaccessWithValidModifiedContent')); + $this->fileAccessHelper + ->expects($this->once()) + ->method('file_put_contents') + ->with( + \OC::$SERVERROOT . '/tests/data/integritycheck/htaccessWithValidModifiedContent/core/signature.json', + $expectedSignatureFileData + ); + + $keyBundle = file_get_contents(__DIR__ .'/../../data/integritycheck/core.crt'); + $rsaPrivateKey = file_get_contents(__DIR__ .'/../../data/integritycheck/core.key'); + $rsa = new RSA(); + $rsa->loadKey($rsaPrivateKey); + $x509 = new X509(); + $x509->loadX509($keyBundle); + $this->checker->writeCoreSignature($x509, $rsa); + } + public function testVerifyCoreSignatureWithoutSignatureData() { $this->environmentHelper ->expects($this->once()) diff --git a/tests/lib/security/credentialsmanager.php b/tests/lib/security/credentialsmanager.php new file mode 100644 index 00000000000..72f061e05bb --- /dev/null +++ b/tests/lib/security/credentialsmanager.php @@ -0,0 +1,102 @@ +<?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); + } + + private function getQeuryResult($row) { + $result = $this->getMockBuilder('\Doctrine\DBAL\Driver\Statement') + ->disableOriginalConstructor() + ->getMock(); + + $result->expects($this->any()) + ->method('fetch') + ->will($this->returnValue($row)); + + return $result; + } + + 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($this->getQeuryResult(['credentials' => 'baz'])); + + $this->dbConnection->expects($this->once()) + ->method('getQueryBuilder') + ->willReturn($qb); + + $this->manager->retrieve($userId, $identifier); + } + +} |