summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2016-11-16 16:17:28 +0100
committerGitHub <noreply@github.com>2016-11-16 16:17:28 +0100
commit61453f5fd5eb3e742d1c757def36ce8f6be408f3 (patch)
tree6dfbb42c7d7f062234e54de6a19f333c97e5aece
parent5f789fdebcd67ff7374becd9fd9cb28727a191de (diff)
parente633f2f8dff0ae99e7621b5c459474887c965c0e (diff)
downloadnextcloud-server-61453f5fd5eb3e742d1c757def36ce8f6be408f3.tar.gz
nextcloud-server-61453f5fd5eb3e742d1c757def36ce8f6be408f3.zip
Merge pull request #719 from nextcloud/lockdown
Allow restricting of app password permissions
-rw-r--r--apps/dav/lib/Connector/Sabre/Auth.php1
-rw-r--r--db_structure.xml7
-rw-r--r--lib/composer/composer/autoload_classmap.php4
-rw-r--r--lib/composer/composer/autoload_static.php4
-rw-r--r--lib/private/Authentication/Token/DefaultToken.php33
-rw-r--r--lib/private/Authentication/Token/DefaultTokenMapper.php31
-rw-r--r--lib/private/Authentication/Token/DefaultTokenProvider.php17
-rw-r--r--lib/private/Authentication/Token/IProvider.php11
-rw-r--r--lib/private/Authentication/Token/IToken.php23
-rw-r--r--lib/private/Files/Filesystem.php42
-rw-r--r--lib/private/Lockdown/Filesystem/NullCache.php122
-rw-r--r--lib/private/Lockdown/Filesystem/NullStorage.php177
-rw-r--r--lib/private/Lockdown/LockdownManager.php46
-rw-r--r--lib/private/Server.php11
-rw-r--r--lib/private/User/Session.php1
-rw-r--r--lib/public/Lockdown/ILockdownManager.php50
-rw-r--r--settings/Controller/AuthSettingsController.php20
-rw-r--r--settings/css/settings.css57
-rw-r--r--settings/js/authtoken_view.js53
-rw-r--r--tests/Settings/Controller/AuthSettingsControllerTest.php39
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenMapperTest.php40
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenProviderTest.php23
-rw-r--r--tests/lib/Authentication/Token/DefaultTokenTest.php49
-rw-r--r--tests/lib/Lockdown/Filesystem/NoFSTest.php63
-rw-r--r--tests/lib/Lockdown/Filesystem/NullCacheTest.php157
-rw-r--r--tests/lib/Lockdown/Filesystem/NullStorageTest.php245
-rw-r--r--tests/lib/Lockdown/LockdownManagerTest.php49
-rw-r--r--version.php2
28 files changed, 1336 insertions, 41 deletions
diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php
index a35eed88073..95222dafec9 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/db_structure.xml b/db_structure.xml
index 09dbde710d3..c7e1e072a8e 100644
--- a/db_structure.xml
+++ b/db_structure.xml
@@ -1152,6 +1152,13 @@
<length>4</length>
</field>
+ <field>
+ <name>scope</name>
+ <type>clob</type>
+ <default></default>
+ <notnull>false</notnull>
+ </field>
+
<index>
<name>authtoken_token_index</name>
<unique>true</unique>
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 42cfb8c45e1..69e8428fdea 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 d7e937577f2..c960a35d951 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',
diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php
index faef2f73b33..127430ea6cb 100644
--- a/lib/private/Authentication/Token/DefaultToken.php
+++ b/lib/private/Authentication/Token/DefaultToken.php
@@ -87,6 +87,17 @@ class DefaultToken extends Entity implements IToken {
*/
protected $lastCheck;
+ /**
+ * @var string
+ */
+ protected $scope;
+
+ public function __construct() {
+ $this->addType('type', 'int');
+ $this->addType('lastActivity', 'int');
+ $this->addType('lastCheck', 'int');
+ }
+
public function getId() {
return $this->id;
}
@@ -119,6 +130,7 @@ class DefaultToken extends Entity implements IToken {
'name' => $this->name,
'lastActivity' => $this->lastActivity,
'type' => $this->type,
+ 'scope' => $this->getScopeAsArray()
];
}
@@ -140,4 +152,25 @@ class DefaultToken extends Entity implements IToken {
return parent::setLastCheck($time);
}
+ public function getScope() {
+ return parent::getScope();
+ }
+
+ public function getScopeAsArray() {
+ $scope = json_decode($this->getScope(), true);
+ if (!$scope) {
+ return [
+ 'filesystem'=> true
+ ];
+ }
+ return $scope;
+ }
+
+ public function setScope($scope) {
+ if (is_array($scope)) {
+ parent::setScope(json_encode($scope));
+ } else {
+ parent::setScope((string)$scope);
+ }
+ }
}
diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php
index 752974ff240..8848cd3ec56 100644
--- a/lib/private/Authentication/Token/DefaultTokenMapper.php
+++ b/lib/private/Authentication/Token/DefaultTokenMapper.php
@@ -72,10 +72,9 @@ 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)
+ ->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
->execute();
$data = $result->fetch();
@@ -83,6 +82,30 @@ class DefaultTokenMapper extends Mapper {
if ($data === false) {
throw new DoesNotExistException('token does not exist');
}
+;
+ return DefaultToken::fromRow($data);
+ }
+
+ /**
+ * Get the token for $id
+ *
+ * @param string $id
+ * @throws DoesNotExistException
+ * @return DefaultToken
+ */
+ 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->createNamedParameter($id)))
+ ->execute();
+
+ $data = $result->fetch();
+ $result->closeCursor();
+ if ($data === false) {
+ throw new DoesNotExistException('token does not exist');
+ };
return DefaultToken::fromRow($data);
}
@@ -98,7 +121,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/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php
index 87f434c684c..0fdbc4a51dd 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
@@ -160,6 +160,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
* @throws InvalidTokenException
diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php
index ce14a5880c5..9f280263d76 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 14811dd3201..49745b266c4 100644
--- a/lib/private/Authentication/Token/IToken.php
+++ b/lib/private/Authentication/Token/IToken.php
@@ -67,9 +67,30 @@ 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
+ *
+ * @return string
+ */
+ public function getScope();
+
+ /**
+ * Get the authentication scope for this token
+ *
+ * @return array
+ */
+ public function getScopeAsArray();
+
+ /**
+ * Set the authentication scope for this token
+ *
+ * @param array $scope
+ */
+ public function setScope($scope);
}
diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php
index 55cf38bbdc9..ac0e66973d4 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);
+ self::listenForNewMountProviders($mountConfigManager, $userManager);
+ } else {
+ self::$mounts->addMount(new MountPoint(
+ new NullStorage([]),
+ '/' . $user
+ ));
+ self::$mounts->addMount(new MountPoint(
+ new NullStorage([]),
+ '/' . $user . '/files'
+ ));
+ }
\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 00000000000..8c6b5258aa8
--- /dev/null
+++ b/lib/private/Lockdown/Filesystem/NullCache.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl>
+ *
+ * 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\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 00000000000..967b6d2c6e7
--- /dev/null
+++ b/lib/private/Lockdown/Filesystem/NullStorage.php
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl>
+ *
+ * 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\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 00000000000..5ce52a03683
--- /dev/null
+++ b/lib/private/Lockdown/LockdownManager.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl>
+ *
+ * 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\Lockdown;
+
+use OC\Authentication\Token\IToken;
+use OCP\Lockdown\ILockdownManager;
+
+class LockdownManager implements ILockdownManager {
+ private $enabled = false;
+
+ /** @var array|null */
+ private $scope;
+
+ public function enable() {
+ $this->enabled = true;
+ }
+
+ public function setToken(IToken $token) {
+ $this->scope = $token->getScopeAsArray();
+ $this->enable();
+ }
+
+ public function canAccessFilesystem() {
+ if (!$this->enabled) {
+ return true;
+ }
+ return !$this->scope || $this->scope['filesystem'];
+ }
+}
diff --git a/lib/private/Server.php b/lib/private/Server.php
index abedf8230ed..c6755357a1d 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 ef408aa4077..6033f060504 100644
--- a/lib/private/User/Session.php
+++ b/lib/private/User/Session.php
@@ -525,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/lib/public/Lockdown/ILockdownManager.php b/lib/public/Lockdown/ILockdownManager.php
new file mode 100644
index 00000000000..d4d05b37ff8
--- /dev/null
+++ b/lib/public/Lockdown/ILockdownManager.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @copyright Copyright (c) 2016, Robin Appelman <robin@icewind.nl>
+ *
+ * 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\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();
+}
diff --git a/settings/Controller/AuthSettingsController.php b/settings/Controller/AuthSettingsController.php
index 58994f0d59c..4e3d05a14e8 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
];
}
@@ -180,4 +182,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 37197b9550c..9008ba5a985 100644
--- a/settings/css/settings.css
+++ b/settings/css/settings.css
@@ -149,6 +149,13 @@ table.nostyle td { padding: 0.2em 0; }
padding: 10px 10px 10px 0;
}
+#sessions .token-list td.more,
+#apppasswords .token-list td.more {
+ overflow: visible;
+ position: relative;
+ width: 16px;
+}
+
#sessions .token-list td,
#apppasswords .token-list td {
border-top: 1px solid #DDD;
@@ -156,18 +163,60 @@ 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-delete,
-#apppasswords .token-list td a.icon-delete {
+#sessions .token-list td > a.icon,
+#apppasswords .token-list td > a.icon {
+ opacity: 0;
+ transition: opacity 0.5s;
+}
+
+#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;
+}
+
+#sessions .token-list tr.active div.configure,
+#apppasswords .token-list tr.active div.configure {
+ display: block;
+ position: absolute;
+ top: 45px;
+ right: -5px;
+ padding: 10px;
+}
+
+#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,
#new-app-password {
width: 186px;
diff --git a/settings/js/authtoken_view.js b/settings/js/authtoken_view.js
index 6eb04b63f20..20fe5235eb0 100644
--- a/settings/js/authtoken_view.js
+++ b/settings/js/authtoken_view.js
@@ -27,13 +27,22 @@
var TEMPLATE_TOKEN =
'<tr data-id="{{id}}">'
- + '<td class="has-tooltip" title="{{title}}"><span class="token-name">{{name}}</span></td>'
+ + '<td class="has-tooltip" title="{{title}}">'
+ + '<span class="token-name">{{name}}</span>'
+ + '</td>'
+ '<td><span class="last-activity has-tooltip" title="{{lastActivityTime}}">{{lastActivity}}</span></td>'
+ + '<td class="more">'
+ + '{{#if showMore}}<a class="icon icon-more"/>{{/if}}'
+ + '<div class="popovermenu bubble open menu configure">'
+ + '{{#if canScope}}'
+ + '<input class="filesystem checkbox" type="checkbox" id="{{id}}_filesystem" {{#if scope.filesystem}}checked{{/if}}/>'
+ + '<label for="{{id}}_filesystem">' + t('core', 'Allow filesystem access') + '</label><br/>'
+ + '{{/if}}'
+ '{{#if canDelete}}'
- + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>'
- + '{{else}}'
- + '<td></td>'
+ + '<a class="icon icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '">' + t('core', 'Revoke') +'</a>'
+ '{{/if}}'
+ + '</div>'
+ + '</td>'
+ '<tr>';
var SubView = OC.Backbone.View.extend({
@@ -70,7 +79,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('');
@@ -78,7 +87,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 +103,13 @@
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;
+ viewData.showMore = viewData.canScope || viewData.canDelete;
// preserve title for cases where we format it further
viewData.title = viewData.name;
@@ -204,6 +216,8 @@
var $el = $(el);
$el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this));
+ $el.on('click', '.icon-more', _.bind(_this._onConfigureToken, _this));
+ $el.on('change', 'input.filesystem', _.bind(_this._onSetTokenScope, _this));
});
this._form = $('#app-password-form');
@@ -325,6 +339,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');
@@ -353,6 +374,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();
diff --git a/tests/Settings/Controller/AuthSettingsControllerTest.php b/tests/Settings/Controller/AuthSettingsControllerTest.php
index 9cb49e4eb3f..782c9f644e0 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;
@@ -94,17 +95,19 @@ class AuthSettingsControllerTest extends TestCase {
[
'id' => 100,
'name' => null,
- 'lastActivity' => null,
- 'type' => null,
+ 'lastActivity' => 0,
+ 'type' => 0,
'canDelete' => false,
'current' => true,
+ 'scope' => ['filesystem' => true]
],
[
'id' => 200,
'name' => null,
- 'lastActivity' => null,
- 'type' => null,
+ 'lastActivity' => 0,
+ 'type' => 0,
'canDelete' => true,
+ 'scope' => ['filesystem' => true]
]
], $this->controller->index());
}
@@ -141,9 +144,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));
@@ -194,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']]));
+ }
+
}
diff --git a/tests/lib/Authentication/Token/DefaultTokenMapperTest.php b/tests/lib/Authentication/Token/DefaultTokenMapperTest.php
index 418a4d14f62..8fe0762daad 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;
@@ -122,7 +125,6 @@ class DefaultTokenMapperTest extends TestCase {
}
public function testGetToken() {
- $token = '1504445f1524fc801035448a95681a9378ba2e83930c814546c56e5d6ebde221198792fd900c88ed5ead0555780dad1ebce3370d7e154941cd5de87eb419899b';
$token = new DefaultToken();
$token->setUid('user2');
$token->setLoginName('User2');
@@ -151,6 +153,42 @@ 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 testGetTokenByIdNotFound() {
+ $this->mapper->getTokenById(-1);
+ }
+
+ /**
+ * @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 5e4d4f94366..8d92ee405a1 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);
+ }
}
diff --git a/tests/lib/Authentication/Token/DefaultTokenTest.php b/tests/lib/Authentication/Token/DefaultTokenTest.php
new file mode 100644
index 00000000000..f00c32ccaf5
--- /dev/null
+++ b/tests/lib/Authentication/Token/DefaultTokenTest.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace 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/Filesystem/NoFSTest.php b/tests/lib/Lockdown/Filesystem/NoFSTest.php
new file mode 100644
index 00000000000..a0900ad769d
--- /dev/null
+++ b/tests/lib/Lockdown/Filesystem/NoFSTest.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * @copyright 2016, Robin Appelman <robin@icewind.nl>
+ *
+ * @author Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace 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/NullCacheTest.php b/tests/lib/Lockdown/Filesystem/NullCacheTest.php
new file mode 100644
index 00000000000..3a4e3f3a402
--- /dev/null
+++ b/tests/lib/Lockdown/Filesystem/NullCacheTest.php
@@ -0,0 +1,157 @@
+<?php
+/**
+ * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace 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 00000000000..dc99eb4c03a
--- /dev/null
+++ b/tests/lib/Lockdown/Filesystem/NullStorageTest.php
@@ -0,0 +1,245 @@
+<?php
+/**
+ * @copyright 2016, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace 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->filemtime('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'));
+ }
+}
diff --git a/tests/lib/Lockdown/LockdownManagerTest.php b/tests/lib/Lockdown/LockdownManagerTest.php
new file mode 100644
index 00000000000..4cbd9d71a5c
--- /dev/null
+++ b/tests/lib/Lockdown/LockdownManagerTest.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016 Robin Appelman <robin@icewind.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace 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());
+ }
+}
diff --git a/version.php b/version.php
index 42a0e7c9bdb..d556386a848 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';