Merge pull request #719 from nextcloud/lockdown

Allow restricting of app password permissions
This commit is contained in:
Roeland Jago Douma 2016-11-16 16:17:28 +01:00 committed by GitHub
commit 61453f5fd5
28 changed files with 1337 additions and 42 deletions

View file

@ -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");
}
}

View file

@ -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>

View file

@ -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',

View file

@ -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',

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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);
}

View file

@ -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));
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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'];
}
}

View file

@ -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');
}
}

View file

@ -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()) {

View file

@ -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();
}

View file

@ -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 [];
}
}

View file

@ -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;

View file

@ -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>'
+ '{{#if canDelete}}'
+ '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>'
+ '{{else}}'
+ '<td></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}}'
+ '<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();

View file

@ -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']]));
}
}

View file

@ -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())

View file

@ -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);
}
}

View file

@ -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());
}
}

View file

@ -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'));
}
}

View file

@ -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 /'));
}
}

View file

@ -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'));
}
}

View file

@ -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());
}
}

View file

@ -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';