From b56f2c9ed01332bbeaee73599a0ea166c62d01e8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 1 Aug 2016 18:27:07 +0200 Subject: [PATCH 01/18] basic lockdown logic Signed-off-by: Robin Appelman --- apps/dav/lib/Connector/Sabre/Auth.php | 1 + lib/private/App/AppManager.php | 5 + lib/private/Files/Filesystem.php | 42 +++-- lib/private/Lockdown/Filesystem/NullCache.php | 122 ++++++++++++ .../Lockdown/Filesystem/NullStorage.php | 177 ++++++++++++++++++ lib/private/Lockdown/LockdownManager.php | 46 +++++ lib/private/Server.php | 11 ++ lib/private/User/Session.php | 2 + lib/private/legacy/app.php | 2 +- lib/public/Lockdown/ILockdownManager.php | 32 ++++ 10 files changed, 424 insertions(+), 16 deletions(-) create mode 100644 lib/private/Lockdown/Filesystem/NullCache.php create mode 100644 lib/private/Lockdown/Filesystem/NullStorage.php create mode 100644 lib/private/Lockdown/LockdownManager.php create mode 100644 lib/public/Lockdown/ILockdownManager.php diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index a35eed8807..95222dafec 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -159,6 +159,7 @@ class Auth extends AbstractBasic { } catch (Exception $e) { $class = get_class($e); $msg = $e->getMessage(); + \OC::$server->getLogger()->logException($e); throw new ServiceUnavailable("$class: $msg"); } } diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 55fd575e12..354f891828 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -112,6 +112,11 @@ class AppManager implements IAppManager { return $value !== 'no'; }); ksort($this->installedAppsCache); + foreach ($this->installedAppsCache as $appId => $value) { + if (!\OC::$server->getLockdownManager()->canAccessApp($appId)) { + unset($this->installedAppsCache[$appId]); + } + } } return $this->installedAppsCache; } diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index 55cf38bbdc..ac0e66973d 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -62,6 +62,7 @@ use OC\Cache\CappedMemoryCache; use OC\Files\Config\MountProviderCollection; use OC\Files\Mount\MountPoint; use OC\Files\Storage\StorageFactory; +use OC\Lockdown\Filesystem\NullStorage; use OCP\Files\Config\IMountProvider; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; @@ -216,7 +217,7 @@ class Filesystem { * @internal */ public static function logWarningWhenAddingStorageWrapper($shouldLog) { - self::$logWarningWhenAddingStorageWrapper = (bool) $shouldLog; + self::$logWarningWhenAddingStorageWrapper = (bool)$shouldLog; } /** @@ -426,25 +427,36 @@ class Filesystem { self::$usersSetup[$user] = true; } - /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ - $mountConfigManager = \OC::$server->getMountProviderCollection(); + if (\OC::$server->getLockdownManager()->canAccessFilesystem()) { + /** @var \OC\Files\Config\MountProviderCollection $mountConfigManager */ + $mountConfigManager = \OC::$server->getMountProviderCollection(); - // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers - $homeMount = $mountConfigManager->getHomeMountForUser($userObject); + // home mounts are handled seperate since we need to ensure this is mounted before we call the other mount providers + $homeMount = $mountConfigManager->getHomeMountForUser($userObject); - self::getMountManager()->addMount($homeMount); + self::getMountManager()->addMount($homeMount); - \OC\Files\Filesystem::getStorage($user); + \OC\Files\Filesystem::getStorage($user); - // Chance to mount for other storages - if ($userObject) { - $mounts = $mountConfigManager->getMountsForUser($userObject); - array_walk($mounts, array(self::$mounts, 'addMount')); - $mounts[] = $homeMount; - $mountConfigManager->registerMounts($userObject, $mounts); + // Chance to mount for other storages + if ($userObject) { + $mounts = $mountConfigManager->getMountsForUser($userObject); + array_walk($mounts, array(self::$mounts, 'addMount')); + $mounts[] = $homeMount; + $mountConfigManager->registerMounts($userObject, $mounts); + } + + self::listenForNewMountProviders($mountConfigManager, $userManager); + } else { + self::$mounts->addMount(new MountPoint( + new NullStorage([]), + '/' . $user + )); + self::$mounts->addMount(new MountPoint( + new NullStorage([]), + '/' . $user . '/files' + )); } - - self::listenForNewMountProviders($mountConfigManager, $userManager); \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', array('user' => $user)); } diff --git a/lib/private/Lockdown/Filesystem/NullCache.php b/lib/private/Lockdown/Filesystem/NullCache.php new file mode 100644 index 0000000000..8c6b5258aa --- /dev/null +++ b/lib/private/Lockdown/Filesystem/NullCache.php @@ -0,0 +1,122 @@ + + * + * 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 + * + */ + +namespace OC\Lockdown\Filesystem; + +use OC\Files\Cache\CacheEntry; +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\FileInfo; + +class NullCache implements ICache { + public function getNumericStorageId() { + return -1; + } + + public function get($file) { + return $file !== '' ? null : + new CacheEntry([ + 'fileid' => -1, + 'parent' => -1, + 'name' => '', + 'path' => '', + 'size' => '0', + 'mtime' => time(), + 'storage_mtime' => time(), + 'etag' => '', + 'mimetype' => FileInfo::MIMETYPE_FOLDER, + 'mimepart' => 'httpd', + 'permissions' => Constants::PERMISSION_READ + ]); + } + + public function getFolderContents($folder) { + return []; + } + + public function getFolderContentsById($fileId) { + return []; + } + + public function put($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function insert($file, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function update($id, array $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getId($file) { + return -1; + } + + public function getParentId($file) { + return -1; + } + + public function inCache($file) { + return $file === ''; + } + + public function remove($file) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function move($source, $target) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getStatus($file) { + return ICache::COMPLETE; + } + + public function search($pattern) { + return []; + } + + public function searchByMime($mimetype) { + return []; + } + + public function searchByTag($tag, $userId) { + return []; + } + + public function getIncomplete() { + return []; + } + + public function getPathById($id) { + return ''; + } + + public function normalize($path) { + return $path; + } + +} diff --git a/lib/private/Lockdown/Filesystem/NullStorage.php b/lib/private/Lockdown/Filesystem/NullStorage.php new file mode 100644 index 0000000000..967b6d2c6e --- /dev/null +++ b/lib/private/Lockdown/Filesystem/NullStorage.php @@ -0,0 +1,177 @@ + + * + * 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 + * + */ + +namespace OC\Lockdown\Filesystem; + +use Icewind\Streams\IteratorDirectory; +use OC\Files\Storage\Common; + +class NullStorage extends Common { + public function __construct($parameters) { + parent::__construct($parameters); + } + + public function getId() { + return 'null'; + } + + public function mkdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rmdir($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function opendir($path) { + return new IteratorDirectory([]); + } + + public function is_dir($path) { + return $path === ''; + } + + public function is_file($path) { + return false; + } + + public function stat($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function filetype($path) { + return ($path === '') ? 'dir' : false; + } + + public function filesize($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function isCreatable($path) { + return false; + } + + public function isReadable($path) { + return $path === ''; + } + + public function isUpdatable($path) { + return false; + } + + public function isDeletable($path) { + return false; + } + + public function isSharable($path) { + return false; + } + + public function getPermissions($path) { + return null; + } + + public function file_exists($path) { + return $path === ''; + } + + public function filemtime($path) { + return ($path === '') ? time() : false; + } + + public function file_get_contents($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function file_put_contents($path, $data) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function unlink($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function rename($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function copy($path1, $path2) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function fopen($path, $mode) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getMimeType($path) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function hash($type, $path, $raw = false) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function free_space($path) { + return 0; + } + + public function touch($path, $mtime = null) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function getLocalFile($path) { + return false; + } + + public function hasUpdated($path, $time) { + return false; + } + + public function getETag($path) { + return ''; + } + + public function isLocal() { + return false; + } + + public function getDirectDownload($path) { + return false; + } + + public function copyFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function moveFromStorage(\OCP\Files\Storage $sourceStorage, $sourceInternalPath, $targetInternalPath) { + throw new \OC\ForbiddenException('This request is not allowed to access the filesystem'); + } + + public function test() { + return true; + } + + public function getOwner($path) { + return null; + } + + public function getCache($path = '', $storage = null) { + return new NullCache(); + } +} diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php new file mode 100644 index 0000000000..9f10646a9d --- /dev/null +++ b/lib/private/Lockdown/LockdownManager.php @@ -0,0 +1,46 @@ + + * + * 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 + * + */ + +namespace OC\Lockdown; + +use OC\Authentication\Token\IToken; +use OCP\Lockdown\ILockdownManager; + +class LockdownManager implements ILockdownManager { + /** @var IToken|null */ + private $token; + + private $enabled = false; + + public function enable() { + $this->enabled = true; + } + + public function setToken(IToken $token) { + $this->token = $token; + } + + public function canAccessFilesystem() { + return true; + } + + public function canAccessApp($app) { + return $app === 'logreader' || $app === 'files' || $app === 'dav'; + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index abedf8230e..c6755357a1 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -69,6 +69,7 @@ use OC\IntegrityCheck\Helpers\FileAccessHelper; use OC\Lock\DBLockingProvider; use OC\Lock\MemcacheLockingProvider; use OC\Lock\NoopLockingProvider; +use OC\Lockdown\LockdownManager; use OC\Mail\Mailer; use OC\Memcache\ArrayCache; use OC\Notification\Manager; @@ -795,6 +796,9 @@ class Server extends ServerContainer implements IServerContainer { $c->getSystemConfig() ); }); + $this->registerService('LockdownManager', function (Server $c) { + return new LockdownManager(); + }); } /** @@ -1534,4 +1538,11 @@ class Server extends ServerContainer implements IServerContainer { $factory = $this->query(\OC\Files\AppData\Factory::class); return $factory->get($app); } + + /** + * @return \OCP\Lockdown\ILockdownManager + */ + public function getLockdownManager() { + return $this->query('LockdownManager'); + } } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index ef408aa407..82af9281a4 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -341,10 +341,12 @@ class Session implements IUserSession, Emitter { if ($isTokenPassword) { $this->session->set('app_password', $password); + \OC::$server->getLockdownManager()->setToken($this->tokenProvider->getToken($password)); } else if($this->supportsCookies($request)) { // Password login, but cookies supported -> create (browser) session token $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); } + \OC::$server->getLockdownManager()->enable(); return true; } diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index c2ff9a5be3..5503b8b525 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -140,7 +140,7 @@ class OC_App { public static function loadApp($app, $checkUpgrade = true) { self::$loadedApps[] = $app; $appPath = self::getAppPath($app); - if($appPath === false) { + if($appPath === false || !\OC::$server->getLockdownManager()->canAccessApp($app)) { return; } diff --git a/lib/public/Lockdown/ILockdownManager.php b/lib/public/Lockdown/ILockdownManager.php new file mode 100644 index 0000000000..0b0c525501 --- /dev/null +++ b/lib/public/Lockdown/ILockdownManager.php @@ -0,0 +1,32 @@ + + * + * 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 + * + */ + +namespace OCP\Lockdown; + +use OC\Authentication\Token\IToken; + +interface ILockdownManager { + public function enable(); + + public function setToken(IToken $token); + + public function canAccessFilesystem(); + + public function canAccessApp($app); +} From 2389e0f25065ca9c7afbc70cc13d555524e363a8 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 1 Aug 2016 19:06:54 +0200 Subject: [PATCH 02/18] read lockdown scope from token Signed-off-by: Robin Appelman --- db_structure.xml | 7 ++++++ .../Authentication/Token/DefaultToken.php | 13 ++++++++++ .../Token/DefaultTokenMapper.php | 4 ++-- lib/private/Authentication/Token/IToken.php | 4 ++++ lib/private/Lockdown/LockdownManager.php | 24 ++++++++++++++----- lib/private/User/Session.php | 3 +-- version.php | 2 +- 7 files changed, 46 insertions(+), 11 deletions(-) diff --git a/db_structure.xml b/db_structure.xml index 09dbde710d..c7e1e072a8 100644 --- a/db_structure.xml +++ b/db_structure.xml @@ -1152,6 +1152,13 @@ 4 + + scope + clob + + false + + authtoken_token_index true diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index faef2f73b3..0c45c9efa5 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -87,6 +87,11 @@ class DefaultToken extends Entity implements IToken { */ protected $lastCheck; + /** + * @var string + */ + protected $scope; + public function getId() { return $this->id; } @@ -119,6 +124,7 @@ class DefaultToken extends Entity implements IToken { 'name' => $this->name, 'lastActivity' => $this->lastActivity, 'type' => $this->type, + 'scope' => $this->getScope() ]; } @@ -140,4 +146,11 @@ class DefaultToken extends Entity implements IToken { return parent::setLastCheck($time); } + public function getScope() { + return json_decode(parent::getScope(), true); + } + + public function setScope($scope) { + return parent::setScope(json_encode($scope)); + } } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 752974ff24..e2a17ca0f9 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -72,7 +72,7 @@ class DefaultTokenMapper extends Mapper { public function getToken($token) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check') + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') ->where($qb->expr()->eq('token', $qb->createParameter('token'))) ->setParameter('token', $token) @@ -98,7 +98,7 @@ class DefaultTokenMapper extends Mapper { public function getTokenByUser(IUser $user) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check') + $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->setMaxResults(1000); diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 14811dd320..3fa8ccbb07 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -72,4 +72,8 @@ interface IToken extends JsonSerializable { * @param int $time */ public function setLastCheck($time); + + public function getScope(); + + public function setScope($scope); } diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php index 9f10646a9d..150b54bdba 100644 --- a/lib/private/Lockdown/LockdownManager.php +++ b/lib/private/Lockdown/LockdownManager.php @@ -23,24 +23,36 @@ use OC\Authentication\Token\IToken; use OCP\Lockdown\ILockdownManager; class LockdownManager implements ILockdownManager { - /** @var IToken|null */ - private $token; - private $enabled = false; + /** @var array|null */ + private $scope; + public function enable() { $this->enabled = true; } public function setToken(IToken $token) { - $this->token = $token; + $this->scope = $token->getScope(); + $this->enable(); } public function canAccessFilesystem() { - return true; + if (!$this->enabled) { + return true; + } + return !$this->scope || $this->scope['filesystem']; } public function canAccessApp($app) { - return $app === 'logreader' || $app === 'files' || $app === 'dav'; + if (!$this->enabled) { + return true; + } + if ($this->scope && $this->scope['apps']) { + return in_array($app, $this->scope['apps']); + } else { + // no limit + return true; + } } } diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 82af9281a4..6033f06050 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -341,12 +341,10 @@ class Session implements IUserSession, Emitter { if ($isTokenPassword) { $this->session->set('app_password', $password); - \OC::$server->getLockdownManager()->setToken($this->tokenProvider->getToken($password)); } else if($this->supportsCookies($request)) { // Password login, but cookies supported -> create (browser) session token $this->createSessionToken($request, $this->getUser()->getUID(), $user, $password); } - \OC::$server->getLockdownManager()->enable(); return true; } @@ -527,6 +525,7 @@ class Session implements IUserSession, Emitter { //login $this->setUser($user); $this->setLoginName($dbToken->getLoginName()); + \OC::$server->getLockdownManager()->setToken($dbToken); $this->manager->emit('\OC\User', 'postLogin', array($user, $password)); if ($this->isLoggedIn()) { diff --git a/version.php b/version.php index 42a0e7c9bd..d556386a84 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ // We only can count up. The 4. digit is only for the internal patchlevel to trigger DB upgrades // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(11, 0, 0, 0); +$OC_Version = array(11, 0, 0, 1); // The human readable string $OC_VersionString = '11.0 alpha'; From b4e27d35f59e359eb7591a15c7f037968081eb1b Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Aug 2016 12:03:18 +0200 Subject: [PATCH 03/18] app password scope wip Signed-off-by: Robin Appelman --- .../Authentication/Token/DefaultToken.php | 6 +++++- .../Token/DefaultTokenMapper.php | 1 + lib/private/Authentication/Token/IToken.php | 14 +++++++++++++- settings/css/settings.css | 10 ++++++++-- settings/js/authtoken_view.js | 19 +++++++++++++------ settings/templates/personal.php | 1 + 6 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 0c45c9efa5..e938ff92e1 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -151,6 +151,10 @@ class DefaultToken extends Entity implements IToken { } public function setScope($scope) { - return parent::setScope(json_encode($scope)); + if (is_string($scope)) { + $this->scope = $scope; + } else { + return parent::setScope(json_encode($scope)); + } } } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index e2a17ca0f9..bfcb54c66c 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -83,6 +83,7 @@ class DefaultTokenMapper extends Mapper { if ($data === false) { throw new DoesNotExistException('token does not exist'); } +; return DefaultToken::fromRow($data); } diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 3fa8ccbb07..a6ba392907 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -67,13 +67,25 @@ interface IToken extends JsonSerializable { public function getLastCheck(); /** - * Get the timestamp of the last password check + * Set the timestamp of the last password check * * @param int $time */ public function setLastCheck($time); + /** + * Get the authentication scope for this token + * + * If the scope is null no limitations exist for the token + * + * @return array|null + */ public function getScope(); + /** + * Set the authentication scope for this token + * + * @param array|null $scope + */ public function setScope($scope); } diff --git a/settings/css/settings.css b/settings/css/settings.css index 7d139a632d..debf69dbae 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -149,6 +149,12 @@ table.nostyle td { padding: 0.2em 0; } padding: 10px 10px 10px 0; } +#sessions .token-list td.icon, +#apppasswords .token-list td.icon { + width: 16px; + padding: 10px; +} + #sessions .token-list td, #apppasswords .token-list td { border-top: 1px solid #DDD; @@ -162,8 +168,8 @@ table.nostyle td { padding: 0.2em 0; } #apppasswords tr *:nth-child(2) { text-align: right; } -#sessions .token-list td a.icon-delete, -#apppasswords .token-list td a.icon-delete { +#sessions .token-list td a.icon, +#apppasswords .token-list td a.icon { display: block; opacity: 0.6; } diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 6eb04b63f2..361b5dcc7a 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -29,11 +29,16 @@ '' + '{{name}}' + '{{lastActivity}}' - + '{{#if canDelete}}' - + '' - + '{{else}}' - + '' + + '' + + '{{#if canScope}}' + + '' + '{{/if}}' + + '' + + '' + + '{{#if canDelete}}' + + '' + + '{{/if}}' + + '' + ''; var SubView = OC.Backbone.View.extend({ @@ -78,7 +83,7 @@ this._toggleHeader(tokens.length > 0); tokens.forEach(function (token) { - var viewData = this._formatViewData(token.toJSON()); + var viewData = this._formatViewData(token); var html = _this.template(viewData); var $html = $(html); $html.find('.has-tooltip').tooltip({container: 'body'}); @@ -94,10 +99,12 @@ this.$('.hidden-when-empty').toggleClass('hidden', !show); }, - _formatViewData: function (viewData) { + _formatViewData: function (token) { + var viewData = token.toJSON(); var ts = viewData.lastActivity * 1000; viewData.lastActivity = OC.Util.relativeModifiedDate(ts); viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); + viewData.canScope = token.get('type') === '1'; // preserve title for cases where we format it further viewData.title = viewData.name; diff --git a/settings/templates/personal.php b/settings/templates/personal.php index ea1c7ba645..c66a9d60d1 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -220,6 +220,7 @@ if($_['passwordChangeSupported']) { t('Name'));?> t('Last activity'));?> + From 1afccde16a04f9a91f9c5c46090517a54670f34d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Aug 2016 15:57:06 +0200 Subject: [PATCH 04/18] allow configuring filesystem access Signed-off-by: Robin Appelman --- .../Authentication/Token/DefaultToken.php | 15 ++++++-- .../Token/DefaultTokenMapper.php | 24 +++++++++++++ .../Token/DefaultTokenProvider.php | 17 ++++++++- .../Authentication/Token/IProvider.php | 11 +++++- lib/private/Authentication/Token/IToken.php | 11 ++++-- lib/private/Lockdown/LockdownManager.php | 2 +- .../Controller/AuthSettingsController.php | 16 +++++++++ settings/css/settings.css | 27 +++++++++++--- settings/js/authtoken_view.js | 35 ++++++++++++++++++- 9 files changed, 145 insertions(+), 13 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index e938ff92e1..b32f9cc986 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -124,7 +124,7 @@ class DefaultToken extends Entity implements IToken { 'name' => $this->name, 'lastActivity' => $this->lastActivity, 'type' => $this->type, - 'scope' => $this->getScope() + 'scope' => $this->getScopeAsArray() ]; } @@ -147,7 +147,18 @@ class DefaultToken extends Entity implements IToken { } public function getScope() { - return json_decode(parent::getScope(), true); + return parent::getScope(); + } + + public function getScopeAsArray() { + $scope = json_decode($this->getScope(), true); + if (!$scope) { + return [ + 'filesystem'=> true, + 'apps' => [] + ]; + } + return $scope; } public function setScope($scope) { diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index bfcb54c66c..32551a9b37 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -87,6 +87,30 @@ class DefaultTokenMapper extends Mapper { return DefaultToken::fromRow($data); } + /** + * Get the user UID for the given token + * + * @param string $token + * @throws DoesNotExistException + * @return DefaultToken + */ + public function getTokenById($token) { + /* @var $qb IQueryBuilder */ + $qb = $this->db->getQueryBuilder(); + $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check', 'scope') + ->from('authtoken') + ->where($qb->expr()->eq('id', $qb->createParameter('id'))) + ->setParameter('id', $token) + ->execute(); + + $data = $result->fetch(); + $result->closeCursor(); + if ($data === false) { + throw new DoesNotExistException('token does not exist'); + }; + return DefaultToken::fromRow($data); + } + /** * Get all token of a user * diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 87f434c684..0fdbc4a51d 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -145,7 +145,7 @@ class DefaultTokenProvider implements IProvider { } /** - * Get a token by token id + * Get a token by token * * @param string $tokenId * @throws InvalidTokenException @@ -159,6 +159,21 @@ class DefaultTokenProvider implements IProvider { } } + /** + * Get a token by token id + * + * @param string $tokenId + * @throws InvalidTokenException + * @return DefaultToken + */ + public function getTokenById($tokenId) { + try { + return $this->mapper->getTokenById($tokenId); + } catch (DoesNotExistException $ex) { + throw new InvalidTokenException(); + } + } + /** * @param string $oldSessionId * @param string $sessionId diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index ce14a5880c..9f280263d7 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -50,7 +50,16 @@ interface IProvider { * @throws InvalidTokenException * @return IToken */ - public function getToken($tokenId) ; + public function getToken($tokenId); + + /** + * Get a token by token id + * + * @param string $tokenId + * @throws InvalidTokenException + * @return DefaultToken + */ + public function getTokenById($tokenId); /** * Duplicate an existing session token diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index a6ba392907..71f52fd6c0 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -76,12 +76,17 @@ interface IToken extends JsonSerializable { /** * Get the authentication scope for this token * - * If the scope is null no limitations exist for the token - * - * @return array|null + * @return string */ public function getScope(); + /** + * Get the authentication scope for this token + * + * @return array + */ + public function getScopeAsArray(); + /** * Set the authentication scope for this token * diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php index 150b54bdba..c34f7e01b6 100644 --- a/lib/private/Lockdown/LockdownManager.php +++ b/lib/private/Lockdown/LockdownManager.php @@ -33,7 +33,7 @@ class LockdownManager implements ILockdownManager { } public function setToken(IToken $token) { - $this->scope = $token->getScope(); + $this->scope = $token->getScopeAsArray(); $this->enable(); } diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index 58994f0d59..f097abf910 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -180,4 +180,20 @@ class AuthSettingsController extends Controller { return []; } + /** + * @NoAdminRequired + * @NoSubadminRequired + * + * @param int $id + * @param array $scope + */ + public function update($id, array $scope) { + $token = $this->tokenProvider->getTokenById($id); + $token->setScope([ + 'filesystem' => $scope['filesystem'], + 'app' => array_values($scope['apps']) + ]); + $this->tokenProvider->updateToken($token); + return []; + } } diff --git a/settings/css/settings.css b/settings/css/settings.css index debf69dbae..7eff9df1d9 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -162,16 +162,35 @@ table.nostyle td { padding: 0.2em 0; } max-width: 200px; white-space: nowrap; overflow: hidden; + vertical-align: top; + position: relative; } -#sessions tr *:nth-child(2), -#apppasswords tr *:nth-child(2) { +#sessions tr>*:nth-child(2), +#apppasswords tr>*:nth-child(2) { text-align: right; } -#sessions .token-list td a.icon, -#apppasswords .token-list td a.icon { +#sessions .token-list a.icon, +#apppasswords .token-list a.icon { display: block; opacity: 0.6; + margin-top: 4px; +} + +#sessions .token-list td div.configure, +#apppasswords .token-list td div.configure { + display: none; +} + +#sessions .token-list tr.active div.configure, +#apppasswords .token-list tr.active div.configure { + display: block; + padding-left: 10px; +} + +#sessions .token-list tr.active .token-name, +#apppasswords .token-list tr.active .token-name { + display: none; } #new-app-login-name, diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 361b5dcc7a..54561ffd1e 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -27,7 +27,13 @@ var TEMPLATE_TOKEN = '' - + '{{name}}' + + '' + + '{{name}}' + + '
' + + '' + + '
' + + '
' + + '' + '{{lastActivity}}' + '' + '{{#if canScope}}' @@ -211,6 +217,8 @@ var $el = $(el); $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this)); + $el.on('click', 'a.icon-settings', _.bind(_this._onConfigureToken, _this)); + $el.on('change', 'input.filesystem', _.bind(_this._onSetTokenScope, _this)); }); this._form = $('#app-password-form'); @@ -332,6 +340,13 @@ this._addAppPasswordBtn.toggleClass('icon-loading-small', state); }, + _onConfigureToken: function (event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + $row.toggleClass('active'); + var id = $row.data('id'); + }, + _onDeleteToken: function (event) { var $target = $(event.target); var $row = $target.closest('tr'); @@ -360,6 +375,24 @@ }); }, + _onSetTokenScope: function (event) { + var $target = $(event.target); + var $row = $target.closest('tr'); + var id = $row.data('id'); + + var token = this.collection.get(id); + if (_.isUndefined(token)) { + // Ignore event + return; + } + + var scope = token.get('scope'); + scope.filesystem = $target.is(":checked"); + + token.set('scope', scope); + token.save(); + }, + _toggleFormResult: function (showForm) { if (showForm) { this._result.slideUp(); From da63af8b021afb209308f16611508ea65399f738 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 3 Aug 2016 16:24:12 +0200 Subject: [PATCH 05/18] enable deleting/configuring new tokens Signed-off-by: Robin Appelman --- settings/Controller/AuthSettingsController.php | 4 +++- settings/css/settings.css | 8 +++++++- settings/js/authtoken_view.js | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php index f097abf910..4e3d05a14e 100644 --- a/settings/Controller/AuthSettingsController.php +++ b/settings/Controller/AuthSettingsController.php @@ -135,11 +135,13 @@ class AuthSettingsController extends Controller { $token = $this->generateRandomDeviceToken(); $deviceToken = $this->tokenProvider->generateToken($token, $this->uid, $loginName, $password, $name, IToken::PERMANENT_TOKEN); + $tokenData = $deviceToken->jsonSerialize(); + $tokenData['canDelete'] = true; return [ 'token' => $token, 'loginName' => $loginName, - 'deviceToken' => $deviceToken + 'deviceToken' => $tokenData ]; } diff --git a/settings/css/settings.css b/settings/css/settings.css index 7eff9df1d9..4ef1f876a6 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -172,14 +172,20 @@ table.nostyle td { padding: 0.2em 0; } } #sessions .token-list a.icon, #apppasswords .token-list a.icon { - display: block; opacity: 0.6; margin-top: 4px; + display: none; +} + +#sessions .token-list tr:hover a.icon, +#apppasswords .token-list tr:hover a.icon { + display: block; } #sessions .token-list td div.configure, #apppasswords .token-list td div.configure { display: none; + height: 18px; } #sessions .token-list tr.active div.configure, diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 54561ffd1e..7ba3ce0b92 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -81,7 +81,7 @@ var list = this.$('.token-list'); var tokens = this.collection.filter(function (token) { - return parseInt(token.get('type'), 10) === _this.type; + return token.get('type') === _this.type; }); list.html(''); @@ -110,7 +110,7 @@ var ts = viewData.lastActivity * 1000; viewData.lastActivity = OC.Util.relativeModifiedDate(ts); viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); - viewData.canScope = token.get('type') === '1'; + viewData.canScope = token.get('type') === 1; // preserve title for cases where we format it further viewData.title = viewData.name; From 7e9e5db4963b2ce8e34b6b70347d9cc2dff3f88a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 4 Aug 2016 12:49:41 +0200 Subject: [PATCH 06/18] fix setscope Signed-off-by: Robin Appelman --- lib/private/Authentication/Token/DefaultToken.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index b32f9cc986..ea2414e6e1 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -163,9 +163,9 @@ class DefaultToken extends Entity implements IToken { public function setScope($scope) { if (is_string($scope)) { - $this->scope = $scope; + parent::setScope($scope); } else { - return parent::setScope(json_encode($scope)); + parent::setScope(json_encode($scope)); } } } From c5df58ec69af0f8ee48543df66f61e3090bdfb17 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 19 Oct 2016 15:11:58 +0200 Subject: [PATCH 07/18] phpdoc Signed-off-by: Robin Appelman --- lib/private/App/AppManager.php | 5 ----- .../Authentication/Token/DefaultToken.php | 3 +-- lib/private/Lockdown/LockdownManager.php | 12 ---------- lib/private/legacy/app.php | 2 +- lib/public/Lockdown/ILockdownManager.php | 22 +++++++++++++++++-- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/private/App/AppManager.php b/lib/private/App/AppManager.php index 354f891828..55fd575e12 100644 --- a/lib/private/App/AppManager.php +++ b/lib/private/App/AppManager.php @@ -112,11 +112,6 @@ class AppManager implements IAppManager { return $value !== 'no'; }); ksort($this->installedAppsCache); - foreach ($this->installedAppsCache as $appId => $value) { - if (!\OC::$server->getLockdownManager()->canAccessApp($appId)) { - unset($this->installedAppsCache[$appId]); - } - } } return $this->installedAppsCache; } diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index ea2414e6e1..8e6774a0a2 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -154,8 +154,7 @@ class DefaultToken extends Entity implements IToken { $scope = json_decode($this->getScope(), true); if (!$scope) { return [ - 'filesystem'=> true, - 'apps' => [] + 'filesystem'=> true ]; } return $scope; diff --git a/lib/private/Lockdown/LockdownManager.php b/lib/private/Lockdown/LockdownManager.php index c34f7e01b6..5ce52a0368 100644 --- a/lib/private/Lockdown/LockdownManager.php +++ b/lib/private/Lockdown/LockdownManager.php @@ -43,16 +43,4 @@ class LockdownManager implements ILockdownManager { } return !$this->scope || $this->scope['filesystem']; } - - public function canAccessApp($app) { - if (!$this->enabled) { - return true; - } - if ($this->scope && $this->scope['apps']) { - return in_array($app, $this->scope['apps']); - } else { - // no limit - return true; - } - } } diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index 5503b8b525..c2ff9a5be3 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -140,7 +140,7 @@ class OC_App { public static function loadApp($app, $checkUpgrade = true) { self::$loadedApps[] = $app; $appPath = self::getAppPath($app); - if($appPath === false || !\OC::$server->getLockdownManager()->canAccessApp($app)) { + if($appPath === false) { return; } diff --git a/lib/public/Lockdown/ILockdownManager.php b/lib/public/Lockdown/ILockdownManager.php index 0b0c525501..d4d05b37ff 100644 --- a/lib/public/Lockdown/ILockdownManager.php +++ b/lib/public/Lockdown/ILockdownManager.php @@ -21,12 +21,30 @@ namespace OCP\Lockdown; use OC\Authentication\Token\IToken; +/** + * @since 9.2 + */ interface ILockdownManager { + /** + * Enable the lockdown restrictions + * + * @since 9.2 + */ public function enable(); + /** + * Set the active token to get the restrictions from and enable the lockdown + * + * @param IToken $token + * @since 9.2 + */ public function setToken(IToken $token); + /** + * Check whether or not filesystem access is allowed + * + * @return bool + * @since 9.2 + */ public function canAccessFilesystem(); - - public function canAccessApp($app); } From bb65d3b03d247d8bdde797af4acb963fe296595d Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 31 Oct 2016 14:01:31 +0100 Subject: [PATCH 08/18] update tests Signed-off-by: Robin Appelman --- tests/Settings/Controller/AuthSettingsControllerTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php index 9cb49e4eb3..f3ba2dd916 100644 --- a/tests/Settings/Controller/AuthSettingsControllerTest.php +++ b/tests/Settings/Controller/AuthSettingsControllerTest.php @@ -98,6 +98,7 @@ class AuthSettingsControllerTest extends TestCase { 'type' => null, 'canDelete' => false, 'current' => true, + 'scope' => ['filesystem' => true] ], [ 'id' => 200, @@ -105,6 +106,7 @@ class AuthSettingsControllerTest extends TestCase { 'lastActivity' => null, 'type' => null, 'canDelete' => true, + 'scope' => ['filesystem' => true] ] ], $this->controller->index()); } @@ -141,9 +143,13 @@ class AuthSettingsControllerTest extends TestCase { ->with($newToken, $this->uid, 'User13', $password, $name, IToken::PERMANENT_TOKEN) ->will($this->returnValue($deviceToken)); + $deviceToken->expects($this->once()) + ->method('jsonSerialize') + ->will($this->returnValue(['dummy' => 'dummy', 'canDelete' => true])); + $expected = [ 'token' => $newToken, - 'deviceToken' => $deviceToken, + 'deviceToken' => ['dummy' => 'dummy', 'canDelete' => true], 'loginName' => 'User13', ]; $this->assertEquals($expected, $this->controller->create($name)); From a4ea20a259dc2d98cda7b2e8d5ca08f3f1b48ff7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 31 Oct 2016 14:26:29 +0100 Subject: [PATCH 09/18] cast to int Signed-off-by: Robin Appelman --- lib/private/Authentication/Token/DefaultToken.php | 4 ++-- tests/Settings/Controller/AuthSettingsControllerTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index 8e6774a0a2..ecfb1a44f2 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -122,8 +122,8 @@ class DefaultToken extends Entity implements IToken { return [ 'id' => $this->id, 'name' => $this->name, - 'lastActivity' => $this->lastActivity, - 'type' => $this->type, + 'lastActivity' => (int)$this->lastActivity, + 'type' => (int)$this->type, 'scope' => $this->getScopeAsArray() ]; } diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php index f3ba2dd916..339c698bcb 100644 --- a/tests/Settings/Controller/AuthSettingsControllerTest.php +++ b/tests/Settings/Controller/AuthSettingsControllerTest.php @@ -94,8 +94,8 @@ class AuthSettingsControllerTest extends TestCase { [ 'id' => 100, 'name' => null, - 'lastActivity' => null, - 'type' => null, + 'lastActivity' => 0, + 'type' => 0, 'canDelete' => false, 'current' => true, 'scope' => ['filesystem' => true] @@ -103,8 +103,8 @@ class AuthSettingsControllerTest extends TestCase { [ 'id' => 200, 'name' => null, - 'lastActivity' => null, - 'type' => null, + 'lastActivity' => 0, + 'type' => 0, 'canDelete' => true, 'scope' => ['filesystem' => true] ] From 4c3d18a9fc255564b082cba94956a7cb8892e946 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 31 Oct 2016 14:38:41 +0100 Subject: [PATCH 10/18] explicit types Signed-off-by: Robin Appelman --- .../Authentication/Token/DefaultToken.php | 16 +++++++++++----- .../Token/DefaultTokenMapperTest.php | 1 - 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index ecfb1a44f2..127430ea6c 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -92,6 +92,12 @@ class DefaultToken extends Entity implements IToken { */ protected $scope; + public function __construct() { + $this->addType('type', 'int'); + $this->addType('lastActivity', 'int'); + $this->addType('lastCheck', 'int'); + } + public function getId() { return $this->id; } @@ -122,8 +128,8 @@ class DefaultToken extends Entity implements IToken { return [ 'id' => $this->id, 'name' => $this->name, - 'lastActivity' => (int)$this->lastActivity, - 'type' => (int)$this->type, + 'lastActivity' => $this->lastActivity, + 'type' => $this->type, 'scope' => $this->getScopeAsArray() ]; } @@ -161,10 +167,10 @@ class DefaultToken extends Entity implements IToken { } public function setScope($scope) { - if (is_string($scope)) { - parent::setScope($scope); - } else { + if (is_array($scope)) { parent::setScope(json_encode($scope)); + } else { + parent::setScope((string)$scope); } } } diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index 418a4d14f6..7520b3c9f6 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -122,7 +122,6 @@ class DefaultTokenMapperTest extends TestCase { } public function testGetToken() { - $token = '1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'; $token = new DefaultToken(); $token->setUid('user2'); $token->setLoginName('User2'); From 4837904ad6c8f6b82f77740f91e08133dc97ffd0 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 31 Oct 2016 14:44:42 +0100 Subject: [PATCH 11/18] update autoloader Signed-off-by: Robin Appelman --- lib/composer/composer/autoload_classmap.php | 4 ++++ lib/composer/composer/autoload_static.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 42cfb8c45e..69e8428fde 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -189,6 +189,7 @@ return array( 'OCP\\LDAP\\ILDAPProviderFactory' => $baseDir . '/lib/public/LDAP/ILDAPProviderFactory.php', 'OCP\\Lock\\ILockingProvider' => $baseDir . '/lib/public/Lock/ILockingProvider.php', 'OCP\\Lock\\LockedException' => $baseDir . '/lib/public/Lock/LockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => $baseDir . '/lib/public/Lockdown/ILockdownManager.php', 'OCP\\Mail\\IMailer' => $baseDir . '/lib/public/Mail/IMailer.php', 'OCP\\Migration\\IOutput' => $baseDir . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => $baseDir . '/lib/public/Migration/IRepairStep.php', @@ -580,6 +581,9 @@ return array( 'OC\\Lock\\DBLockingProvider' => $baseDir . '/lib/private/Lock/DBLockingProvider.php', 'OC\\Lock\\MemcacheLockingProvider' => $baseDir . '/lib/private/Lock/MemcacheLockingProvider.php', 'OC\\Lock\\NoopLockingProvider' => $baseDir . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => $baseDir . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => $baseDir . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => $baseDir . '/lib/private/Lockdown/LockdownManager.php', 'OC\\Log' => $baseDir . '/lib/private/Log.php', 'OC\\Log\\ErrorHandler' => $baseDir . '/lib/private/Log/ErrorHandler.php', 'OC\\Log\\Errorlog' => $baseDir . '/lib/private/Log/Errorlog.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d7e937577f..c960a35d95 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -219,6 +219,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\LDAP\\ILDAPProviderFactory' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProviderFactory.php', 'OCP\\Lock\\ILockingProvider' => __DIR__ . '/../../..' . '/lib/public/Lock/ILockingProvider.php', 'OCP\\Lock\\LockedException' => __DIR__ . '/../../..' . '/lib/public/Lock/LockedException.php', + 'OCP\\Lockdown\\ILockdownManager' => __DIR__ . '/../../..' . '/lib/public/Lockdown/ILockdownManager.php', 'OCP\\Mail\\IMailer' => __DIR__ . '/../../..' . '/lib/public/Mail/IMailer.php', 'OCP\\Migration\\IOutput' => __DIR__ . '/../../..' . '/lib/public/Migration/IOutput.php', 'OCP\\Migration\\IRepairStep' => __DIR__ . '/../../..' . '/lib/public/Migration/IRepairStep.php', @@ -610,6 +611,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Lock\\DBLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/DBLockingProvider.php', 'OC\\Lock\\MemcacheLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/MemcacheLockingProvider.php', 'OC\\Lock\\NoopLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/NoopLockingProvider.php', + 'OC\\Lockdown\\Filesystem\\NullCache' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullCache.php', + 'OC\\Lockdown\\Filesystem\\NullStorage' => __DIR__ . '/../../..' . '/lib/private/Lockdown/Filesystem/NullStorage.php', + 'OC\\Lockdown\\LockdownManager' => __DIR__ . '/../../..' . '/lib/private/Lockdown/LockdownManager.php', 'OC\\Log' => __DIR__ . '/../../..' . '/lib/private/Log.php', 'OC\\Log\\ErrorHandler' => __DIR__ . '/../../..' . '/lib/private/Log/ErrorHandler.php', 'OC\\Log\\Errorlog' => __DIR__ . '/../../..' . '/lib/private/Log/Errorlog.php', From 91851c37be39e0d45e7edd4f770e0254d1ae75fa Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 10 Nov 2016 15:57:31 +0100 Subject: [PATCH 12/18] add tests Signed-off-by: Robin Appelman --- .../Authentication/Token/DefaultTokenTest.php | 49 +++++++++++++++++++ tests/lib/Lockdown/LockdownManagerTest.php | 49 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 tests/lib/Authentication/Token/DefaultTokenTest.php create mode 100644 tests/lib/Lockdown/LockdownManagerTest.php diff --git a/tests/lib/Authentication/Token/DefaultTokenTest.php b/tests/lib/Authentication/Token/DefaultTokenTest.php new file mode 100644 index 0000000000..f00c32ccaf --- /dev/null +++ b/tests/lib/Authentication/Token/DefaultTokenTest.php @@ -0,0 +1,49 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Authentication\Token; + +use OC\Authentication\Token\DefaultToken; +use Test\TestCase; + +class DefaultTokenTest extends TestCase { + public function testSetScopeAsArray() { + $scope = ['filesystem' => false]; + $token = new DefaultToken(); + $token->setScope($scope); + $this->assertEquals(json_encode($scope), $token->getScope()); + $this->assertEquals($scope, $token->getScopeAsArray()); + } + + public function testSetScopeAsString() { + $scope = ['filesystem' => false]; + $token = new DefaultToken(); + $token->setScope(json_encode($scope)); + $this->assertEquals(json_encode($scope), $token->getScope()); + $this->assertEquals($scope, $token->getScopeAsArray()); + } + + public function testDefaultScope() { + $scope = ['filesystem' => true]; + $token = new DefaultToken(); + $this->assertEquals($scope, $token->getScopeAsArray()); + } +} diff --git a/tests/lib/Lockdown/LockdownManagerTest.php b/tests/lib/Lockdown/LockdownManagerTest.php new file mode 100644 index 0000000000..4cbd9d71a5 --- /dev/null +++ b/tests/lib/Lockdown/LockdownManagerTest.php @@ -0,0 +1,49 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Lockdown; + +use OC\Authentication\Token\DefaultToken; +use OC\Lockdown\LockdownManager; +use Test\TestCase; + +class LockdownManagerTest extends TestCase { + public function testCanAccessFilesystemDisabled() { + $manager = new LockdownManager(); + $this->assertTrue($manager->canAccessFilesystem()); + } + + public function testCanAccessFilesystemAllowed() { + $token = new DefaultToken(); + $token->setScope(['filesystem' => true]); + $manager = new LockdownManager(); + $manager->setToken($token); + $this->assertTrue($manager->canAccessFilesystem()); + } + + public function testCanAccessFilesystemNotAllowed() { + $token = new DefaultToken(); + $token->setScope(['filesystem' => false]); + $manager = new LockdownManager(); + $manager->setToken($token); + $this->assertFalse($manager->canAccessFilesystem()); + } +} From e5bc80b31dee6b08fc42d35a4930e23685f167eb Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 11 Nov 2016 09:25:42 +0100 Subject: [PATCH 13/18] Adds TokenProvider and Mapper tests Signed-off-by: Roeland Jago Douma --- .../Token/DefaultTokenMapper.php | 12 +++---- .../Token/DefaultTokenMapperTest.php | 32 +++++++++++++++++++ .../Token/DefaultTokenProviderTest.php | 23 +++++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 32551a9b37..8848cd3ec5 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -74,8 +74,7 @@ class DefaultTokenMapper extends Mapper { $qb = $this->db->getQueryBuilder(); $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') - ->where($qb->expr()->eq('token', $qb->createParameter('token'))) - ->setParameter('token', $token) + ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) ->execute(); $data = $result->fetch(); @@ -88,19 +87,18 @@ class DefaultTokenMapper extends Mapper { } /** - * Get the user UID for the given token + * Get the token for $id * - * @param string $token + * @param string $id * @throws DoesNotExistException * @return DefaultToken */ - public function getTokenById($token) { + public function getTokenById($id) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check', 'scope') ->from('authtoken') - ->where($qb->expr()->eq('id', $qb->createParameter('id'))) - ->setParameter('id', $token) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) ->execute(); $data = $result->fetch(); diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index 7520b3c9f6..4e5740319e 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -27,6 +27,7 @@ use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\DefaultTokenMapper; use OC\Authentication\Token\IToken; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; use OCP\IUser; use Test\TestCase; @@ -40,6 +41,8 @@ class DefaultTokenMapperTest extends TestCase { /** @var DefaultTokenMapper */ private $mapper; + + /** @var IDBConnection */ private $dbConnection; private $time; @@ -150,6 +153,35 @@ class DefaultTokenMapperTest extends TestCase { $this->mapper->getToken($token); } + public function testGetTokenById() { + $token = new DefaultToken(); + $token->setUid('user2'); + $token->setLoginName('User2'); + $token->setPassword('971a337057853344700bbeccf836519f|UwOQwyb34sJHtqPV|036d4890f8c21d17bbc7b88072d8ef049a5c832a38e97f3e3d5f9186e896c2593aee16883f617322fa242728d0236ff32d163caeb4bd45e14ca002c57a88665f'); + $token->setName('Firefox on Android'); + $token->setToken('1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b'); + $token->setType(IToken::TEMPORARY_TOKEN); + $token->setRemember(IToken::DO_NOT_REMEMBER); + $token->setLastActivity($this->time - 60 * 60 * 24 * 3); + $token->setLastCheck($this->time - 10); + + $dbToken = $this->mapper->getToken($token->getToken()); + $token->setId($dbToken->getId()); // We don't know the ID + $token->resetUpdatedFields(); + + $dbToken = $this->mapper->getTokenById($token->getId()); + $this->assertEquals($token, $dbToken); + } + + /** + * @expectedException \OCP\AppFramework\Db\DoesNotExistException + */ + public function testGetInvalidTokenById() { + $id = 42; + + $this->mapper->getToken($id); + } + public function testGetTokenByUser() { $user = $this->createMock(IUser::class); $user->expects($this->once()) diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php index 5e4d4f9436..8d92ee405a 100644 --- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php @@ -22,9 +22,11 @@ namespace Test\Authentication\Token; +use OC\Authentication\Exceptions\InvalidTokenException; use OC\Authentication\Token\DefaultToken; use OC\Authentication\Token\DefaultTokenProvider; use OC\Authentication\Token\IToken; +use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Mapper; use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; @@ -376,4 +378,25 @@ class DefaultTokenProviderTest extends TestCase { $this->tokenProvider->renewSessionToken('oldId', 'newId'); } + public function testGetTokenById() { + $token = $this->createMock(DefaultToken::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + $this->assertSame($token, $this->tokenProvider->getTokenById(42)); + } + + public function testGetInvalidTokenById() { + $this->expectException(InvalidTokenException::class); + + $this->mapper->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willThrowException(new DoesNotExistException('nope')); + + $this->tokenProvider->getTokenById(42); + } } From 59d6003f898e82f00436aa251502e73f796ac725 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 11 Nov 2016 09:43:25 +0100 Subject: [PATCH 14/18] Adds NullCache ans NullStorage tests for Lockdown Signed-off-by: Roeland Jago Douma --- .../lib/Lockdown/Filesystem/NullCacheTest.php | 157 +++++++++++ .../Lockdown/Filesystem/NullStorageTest.php | 245 ++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 tests/lib/Lockdown/Filesystem/NullCacheTest.php create mode 100644 tests/lib/Lockdown/Filesystem/NullStorageTest.php diff --git a/tests/lib/Lockdown/Filesystem/NullCacheTest.php b/tests/lib/Lockdown/Filesystem/NullCacheTest.php new file mode 100644 index 0000000000..3a4e3f3a40 --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NullCacheTest.php @@ -0,0 +1,157 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Lockdown\Filesystem; + +use OC\ForbiddenException; +use OC\Lockdown\Filesystem\NullCache; +use OCP\Constants; +use OCP\Files\Cache\ICache; +use OCP\Files\FileInfo; + +class NulLCacheTest extends \Test\TestCase { + + /** @var NullCache */ + private $cache; + + public function setUp() { + parent::setUp(); + + $this->cache = new NullCache(); + } + + public function testGetNumericStorageId() { + $this->assertSame(-1, $this->cache->getNumericStorageId()); + } + + public function testGetEmpty() { + $this->assertNull($this->cache->get('foo')); + } + + public function testGet() { + $data = $this->cache->get(''); + + $this->assertEquals(-1, $data['fileid']); + $this->assertEquals(-1, $data['parent']); + $this->assertEquals('', $data['name']); + $this->assertEquals('', $data['path']); + $this->assertEquals('0', $data['size']); + $this->assertEquals('', $data['etag']); + $this->assertEquals(FileInfo::MIMETYPE_FOLDER, $data['mimetype']); + $this->assertEquals('httpd', $data['mimepart']); + $this->assertEquals(Constants::PERMISSION_READ, $data['permissions']); + } + + public function testGetFolderContents() { + $this->assertSame([], $this->cache->getFolderContents('foo')); + } + + public function testGetFolderContentsById() { + $this->assertSame([], $this->cache->getFolderContentsById(42)); + } + + public function testPut() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->put('foo', ['size' => 100]); + } + + public function testInsert() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->insert('foo', ['size' => 100]); + } + + public function testUpdate() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->update('foo', ['size' => 100]); + } + + public function testGetId() { + $this->assertSame(-1, $this->cache->getId('foo')); + } + + public function testGetParentId() { + $this->assertSame(-1, $this->cache->getParentId('foo')); + } + + public function testInCache() { + $this->assertTrue($this->cache->inCache('')); + $this->assertFalse($this->cache->inCache('foo')); + } + + public function testRemove() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->remove('foo'); + } + + public function testMove() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->move('foo', 'bar'); + } + + public function testMoveFromCache() { + $sourceCache = $this->createMock(ICache::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->cache->moveFromCache($sourceCache, 'foo', 'bar'); + } + + public function testGetStatus() { + $this->assertSame(ICache::COMPLETE, $this->cache->getStatus('foo')); + } + + public function testSearch() { + $this->assertSame([], $this->cache->search('foo')); + } + + public function testSearchByMime() { + $this->assertSame([], $this->cache->searchByMime('foo')); + } + + public function testSearchByTag() { + $this->assertSame([], $this->cache->searchByTag('foo', 'user')); + } + + public function testGetIncomplete() { + $this->assertSame([], $this->cache->getIncomplete()); + } + + public function testGetPathById() { + $this->assertSame('', $this->cache->getPathById(42)); + } + + public function testNormalize() { + $this->assertSame('foo/ bar /', $this->cache->normalize('foo/ bar /')); + } +} diff --git a/tests/lib/Lockdown/Filesystem/NullStorageTest.php b/tests/lib/Lockdown/Filesystem/NullStorageTest.php new file mode 100644 index 0000000000..1e70cdff20 --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NullStorageTest.php @@ -0,0 +1,245 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Lockdown\Filesystem; + +use Icewind\Streams\IteratorDirectory; +use OC\ForbiddenException; +use OC\Lockdown\Filesystem\NullCache; +use OC\Lockdown\Filesystem\NullStorage; +use OCP\Files\Storage; +use Test\TestCase; + +class NullStorageTest extends TestCase { + + /** @var NullStorage */ + private $storage; + + public function setUp() { + parent::setUp(); + + $this->storage = new NullStorage([]); + } + + public function testGetId() { + $this->assertSame('null', $this->storage->getId()); + } + + public function testMkdir() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->mkdir('foo'); + } + + public function testRmdir() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->rmdir('foo'); + } + + public function testOpendir() { + $this->assertInstanceOf(IteratorDirectory::class, $this->storage->opendir('foo')); + } + + public function testIs_dir() { + $this->assertTrue($this->storage->is_dir('')); + $this->assertFalse($this->storage->is_dir('foo')); + } + + public function testIs_file() { + $this->assertFalse($this->storage->is_file('foo')); + } + + public function testStat() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->stat('foo'); + } + + public function testFiletype() { + $this->assertSame('dir', $this->storage->filetype('')); + $this->assertFalse($this->storage->filetype('foo')); + } + + public function testFilesize() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->filesize('foo'); + } + + public function testIsCreatable() { + $this->assertFalse($this->storage->isCreatable('foo')); + } + + public function testIsReadable() { + $this->assertTrue($this->storage->isReadable('')); + $this->assertFalse($this->storage->isReadable('foo')); + } + + public function testIsUpdatable() { + $this->assertFalse($this->storage->isUpdatable('foo')); + } + + public function testIsDeletable() { + $this->assertFalse($this->storage->isDeletable('foo')); + } + + public function testIsSharable() { + $this->assertFalse($this->storage->isSharable('foo')); + } + + public function testGetPermissions() { + $this->assertNull($this->storage->getPermissions('foo')); + } + + public function testFile_exists() { + $this->assertTrue($this->storage->file_exists('')); + $this->assertFalse($this->storage->file_exists('foo')); + } + + public function testFilemtime() { + $this->assertFalse($this->storage->isReadable('foo')); + } + + public function testFile_get_contents() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->file_get_contents('foo'); + } + + public function testFile_put_contents() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->file_put_contents('foo', 'bar'); + } + + public function testUnlink() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->unlink('foo'); + } + + public function testRename() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->rename('foo', 'bar'); + } + + public function testCopy() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->copy('foo', 'bar'); + } + + public function testFopen() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->fopen('foo', 'R'); + } + + public function testGetMimeType() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->getMimeType('foo'); + } + + public function testHash() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->hash('md5', 'foo', true); + } + + public function testFree_space() { + $this->assertSame(0, $this->storage->free_space('foo')); + } + + public function testTouch() { + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->touch('foo'); + } + + public function testGetLocalFile() { + $this->assertFalse($this->storage->getLocalFile('foo')); + } + + public function testHasUpdated() { + $this->assertFalse($this->storage->hasUpdated('foo', 42)); + } + + public function testGetETag() { + $this->assertSame('', $this->storage->getETag('foo')); + } + + public function testIsLocal() { + $this->assertFalse($this->storage->isLocal()); + } + + public function testGetDirectDownload() { + $this->assertFalse($this->storage->getDirectDownload('foo')); + } + + public function testCopyFromStorage() { + $sourceStorage = $this->createMock(Storage::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->copyFromStorage($sourceStorage, 'foo', 'bar'); + } + + public function testMoveFromStorage() { + $sourceStorage = $this->createMock(Storage::class); + + $this->expectException(ForbiddenException::class); + $this->expectExceptionMessage('This request is not allowed to access the filesystem'); + + $this->storage->moveFromStorage($sourceStorage, 'foo', 'bar'); + } + + public function testTest() { + $this->assertTrue($this->storage->test()); + return true; + } + + public function testGetOwner() { + $this->assertNull($this->storage->getOwner('foo')); + } + + public function testGetCache() { + $this->assertInstanceOf(NullCache::class, $this->storage->getCache('foo')); + } +} From 311531ecce497663960877fc536ba94deff27bc0 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 11 Nov 2016 11:35:11 +0100 Subject: [PATCH 15/18] Adds tests for the AuthSettingsController Signed-off-by: Roeland Jago Douma --- .../Controller/AuthSettingsControllerTest.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php index 339c698bcb..782c9f644e 100644 --- a/tests/Settings/Controller/AuthSettingsControllerTest.php +++ b/tests/Settings/Controller/AuthSettingsControllerTest.php @@ -42,6 +42,7 @@ class AuthSettingsControllerTest extends TestCase { /** @var AuthSettingsController */ private $controller; private $request; + /** @var IProvider|\PHPUnit_Framework_MockObject_MockObject */ private $tokenProvider; private $userManager; private $session; @@ -200,4 +201,26 @@ class AuthSettingsControllerTest extends TestCase { $this->assertEquals([], $this->controller->destroy($id)); } + public function testUpdateToken() { + $token = $this->createMock(DefaultToken::class); + + $this->tokenProvider->expects($this->once()) + ->method('getTokenById') + ->with($this->equalTo(42)) + ->willReturn($token); + + $token->expects($this->once()) + ->method('setScope') + ->with($this->equalTo([ + 'filesystem' => true, + 'app' => ['dav', 'myapp'] + ])); + + $this->tokenProvider->expects($this->once()) + ->method('updateToken') + ->with($this->equalTo($token)); + + $this->assertSame([], $this->controller->update(42, ['filesystem' => true, 'apps' => ['dav', 'myapp']])); + } + } From 9157f807cc9a8a0aa5c68de572bff230dd7e2a49 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 14 Nov 2016 18:19:27 +0100 Subject: [PATCH 16/18] dropdown ui Signed-off-by: Robin Appelman --- settings/css/settings.css | 48 ++++++++++++++++++++++----------- settings/js/authtoken_view.js | 19 +++++++------ settings/templates/personal.php | 1 - 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/settings/css/settings.css b/settings/css/settings.css index 4ef1f876a6..554fe1da81 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -149,10 +149,11 @@ table.nostyle td { padding: 0.2em 0; } padding: 10px 10px 10px 0; } -#sessions .token-list td.icon, -#apppasswords .token-list td.icon { +#sessions .token-list td.more, +#apppasswords .token-list td.more { + overflow: visible; + position: relative; width: 16px; - padding: 10px; } #sessions .token-list td, @@ -170,33 +171,50 @@ table.nostyle td { padding: 0.2em 0; } #apppasswords tr>*:nth-child(2) { text-align: right; } -#sessions .token-list a.icon, -#apppasswords .token-list a.icon { - opacity: 0.6; - margin-top: 4px; - display: none; +#sessions .token-list td > a.icon, +#apppasswords .token-list td > a.icon { + opacity: 0; + transition: opacity 0.5s; } -#sessions .token-list tr:hover a.icon, -#apppasswords .token-list tr:hover a.icon { +#sessions .token-list a.icon, +#apppasswords .token-list a.icon { + margin-top: 4px; display: block; } +#sessions .token-list tr:hover td > a.icon, +#apppasswords .token-list tr:hover td > a.icon, +#sessions .token-list tr.active td > a.icon, +#apppasswords .token-list tr.active td > a.icon{ + opacity: 0.6; +} + #sessions .token-list td div.configure, #apppasswords .token-list td div.configure { display: none; - height: 18px; } #sessions .token-list tr.active div.configure, #apppasswords .token-list tr.active div.configure { display: block; - padding-left: 10px; + position: absolute; + top: 45px; + right: -5px; + padding: 10px; } -#sessions .token-list tr.active .token-name, -#apppasswords .token-list tr.active .token-name { - display: none; +#sessions .token-list tr.active div.configure > *, +#apppasswords .token-list tr.active div.configure > *{ + margin-top: 5px; + margin-bottom: 5px; + display: inline-block; +} + +#sessions .token-list tr.active a.icon-delete, +#apppasswords .token-list tr.active a.icon-delete { + background-position: left; + padding-left: 20px; } #new-app-login-name, diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js index 7ba3ce0b92..20fe5235eb 100644 --- a/settings/js/authtoken_view.js +++ b/settings/js/authtoken_view.js @@ -29,21 +29,19 @@ '' + '' + '{{name}}' - + '
' - + '' - + '
' - + '
' + '' + '{{lastActivity}}' - + '' + + '' + + '{{#if showMore}}{{/if}}' + + '' + '' + ''; @@ -111,6 +109,7 @@ viewData.lastActivity = OC.Util.relativeModifiedDate(ts); viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL'); viewData.canScope = token.get('type') === 1; + viewData.showMore = viewData.canScope || viewData.canDelete; // preserve title for cases where we format it further viewData.title = viewData.name; @@ -217,7 +216,7 @@ var $el = $(el); $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this)); - $el.on('click', 'a.icon-settings', _.bind(_this._onConfigureToken, _this)); + $el.on('click', '.icon-more', _.bind(_this._onConfigureToken, _this)); $el.on('change', 'input.filesystem', _.bind(_this._onSetTokenScope, _this)); }); diff --git a/settings/templates/personal.php b/settings/templates/personal.php index c66a9d60d1..ea1c7ba645 100644 --- a/settings/templates/personal.php +++ b/settings/templates/personal.php @@ -220,7 +220,6 @@ if($_['passwordChangeSupported']) { t('Name'));?> t('Last activity'));?> - From e77432783b0e54033aa827c7dbc20e1d3551a118 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Tue, 15 Nov 2016 17:25:28 +0100 Subject: [PATCH 17/18] Add test for setting up fake fs Signed-off-by: Robin Appelman --- lib/private/Authentication/Token/IToken.php | 2 +- tests/lib/Lockdown/Filesystem/NoFSTest.php | 63 +++++++++++++++++++ .../Lockdown/Filesystem/NullStorageTest.php | 2 +- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/lib/Lockdown/Filesystem/NoFSTest.php diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index 71f52fd6c0..49745b266c 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -90,7 +90,7 @@ interface IToken extends JsonSerializable { /** * Set the authentication scope for this token * - * @param array|null $scope + * @param array $scope */ public function setScope($scope); } diff --git a/tests/lib/Lockdown/Filesystem/NoFSTest.php b/tests/lib/Lockdown/Filesystem/NoFSTest.php new file mode 100644 index 0000000000..a0900ad769 --- /dev/null +++ b/tests/lib/Lockdown/Filesystem/NoFSTest.php @@ -0,0 +1,63 @@ + + * + * @author Robin Appelman + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace Test\Lockdown\Filesystem; + +use OC\Authentication\Token\DefaultToken; +use OC\Files\Filesystem; +use OC\Lockdown\Filesystem\NullStorage; +use Test\Traits\UserTrait; + +/** + * @group DB + */ +class NoFSTest extends \Test\TestCase { + use UserTrait; + + public function tearDown() { + $token = new DefaultToken(); + $token->setScope([ + 'filesystem' => true + ]); + \OC::$server->getLockdownManager()->setToken($token); + return parent::tearDown(); + } + + public function setUp() { + parent::setUp(); + $token = new DefaultToken(); + $token->setScope([ + 'filesystem' => false + ]); + + \OC::$server->getLockdownManager()->setToken($token); + $this->createUser('foo', 'var'); + } + + public function testSetupFS() { + \OC_Util::tearDownFS(); + \OC_Util::setupFS('foo'); + + $this->assertInstanceOf(NullStorage::class, Filesystem::getStorage('/foo/files')); + } +} diff --git a/tests/lib/Lockdown/Filesystem/NullStorageTest.php b/tests/lib/Lockdown/Filesystem/NullStorageTest.php index 1e70cdff20..dc99eb4c03 100644 --- a/tests/lib/Lockdown/Filesystem/NullStorageTest.php +++ b/tests/lib/Lockdown/Filesystem/NullStorageTest.php @@ -122,7 +122,7 @@ class NullStorageTest extends TestCase { } public function testFilemtime() { - $this->assertFalse($this->storage->isReadable('foo')); + $this->assertFalse($this->storage->filemtime('foo')); } public function testFile_get_contents() { From e633f2f8dff0ae99e7621b5c459474887c965c0e Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 16 Nov 2016 15:10:01 +0100 Subject: [PATCH 18/18] add test Signed-off-by: Robin Appelman --- tests/lib/Authentication/Token/DefaultTokenMapperTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php index 4e5740319e..8fe0762daa 100644 --- a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php +++ b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php @@ -173,6 +173,13 @@ class DefaultTokenMapperTest extends TestCase { $this->assertEquals($token, $dbToken); } + /** + * @expectedException \OCP\AppFramework\Db\DoesNotExistException + */ + public function testGetTokenByIdNotFound() { + $this->mapper->getTokenById(-1); + } + /** * @expectedException \OCP\AppFramework\Db\DoesNotExistException */