diff options
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/AppFramework/OCS/BaseResponse.php | 58 | ||||
-rw-r--r-- | lib/private/Authentication/Exceptions/ExpiredTokenException.php | 41 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultToken.php | 116 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultTokenMapper.php | 27 | ||||
-rw-r--r-- | lib/private/Authentication/Token/DefaultTokenProvider.php | 77 | ||||
-rw-r--r-- | lib/private/Authentication/Token/IProvider.php | 41 | ||||
-rw-r--r-- | lib/private/Authentication/Token/IToken.php | 51 | ||||
-rw-r--r-- | lib/private/Files/AppData/AppData.php | 16 | ||||
-rw-r--r-- | lib/private/Files/AppData/Factory.php | 3 | ||||
-rw-r--r-- | lib/private/Preview/BackgroundCleanupJob.php | 91 | ||||
-rw-r--r-- | lib/private/Preview/Watcher.php | 48 | ||||
-rw-r--r-- | lib/private/Preview/WatcherConnector.php | 11 | ||||
-rw-r--r-- | lib/private/Repair.php | 4 | ||||
-rw-r--r-- | lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php | 48 | ||||
-rw-r--r-- | lib/private/Setup.php | 2 |
15 files changed, 480 insertions, 154 deletions
diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php index 59b8660a382..b27784cfcf2 100644 --- a/lib/private/AppFramework/OCS/BaseResponse.php +++ b/lib/private/AppFramework/OCS/BaseResponse.php @@ -22,6 +22,7 @@ */ namespace OC\AppFramework\OCS; +use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\Response; @@ -85,9 +86,60 @@ abstract class BaseResponse extends Response { * @param string[] $meta * @return string */ - protected function renderResult($meta) { - // TODO rewrite functions - return \OC_API::renderResult($this->format, $meta, $this->data); + protected function renderResult(array $meta): string { + $status = $this->getStatus(); + if ($status === Http::STATUS_NO_CONTENT || + $status === Http::STATUS_NOT_MODIFIED || + ($status >= 100 && $status <= 199)) { + // Those status codes are not supposed to have a body: + // https://stackoverflow.com/q/8628725 + return ''; + } + + $response = [ + 'ocs' => [ + 'meta' => $meta, + 'data' => $this->data, + ], + ]; + + if ($this->format === 'json') { + return json_encode($response, JSON_HEX_TAG); + } + + $writer = new \XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->startDocument(); + $this->toXML($response, $writer); + $writer->endDocument(); + return $writer->outputMemory(true); + + } + + /** + * @param array $array + * @param \XMLWriter $writer + */ + protected function toXML(array $array, \XMLWriter $writer) { + foreach ($array as $k => $v) { + if ($k[0] === '@') { + $writer->writeAttribute(substr($k, 1), $v); + continue; + } + + if (\is_numeric($k)) { + $k = 'element'; + } + + if (\is_array($v)) { + $writer->startElement($k); + $this->toXML($v, $writer); + $writer->endElement(); + } else { + $writer->writeElement($k, $v); + } + } } public function getOCSStatus() { diff --git a/lib/private/Authentication/Exceptions/ExpiredTokenException.php b/lib/private/Authentication/Exceptions/ExpiredTokenException.php new file mode 100644 index 00000000000..a45ca5b6955 --- /dev/null +++ b/lib/private/Authentication/Exceptions/ExpiredTokenException.php @@ -0,0 +1,41 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 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 OC\Authentication\Token; + +use OC\Authentication\Exceptions\InvalidTokenException; + +class ExpiredTokenException extends InvalidTokenException { + /** @var IToken */ + private $token; + + public function __construct(IToken $token) { + parent::__construct(); + + $this->token = $token; + } + + public function getToken(): IToken { + return $this->token; + } +} diff --git a/lib/private/Authentication/Token/DefaultToken.php b/lib/private/Authentication/Token/DefaultToken.php index e06803d0bfc..67aa89ea66b 100644 --- a/lib/private/Authentication/Token/DefaultToken.php +++ b/lib/private/Authentication/Token/DefaultToken.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -28,82 +29,69 @@ use OCP\AppFramework\Db\Entity; /** * @method void setId(int $id) * @method void setUid(string $uid); - * @method void setLoginName(string $loginName) - * @method void setPassword(string $password) + * @method void setLoginName(string $loginname) * @method void setName(string $name) - * @method string getName() - * @method void setToken(string $token) * @method string getToken() - * @method void setType(string $type) + * @method void setType(int $type) * @method int getType() * @method void setRemember(int $remember) - * @method int getRemember() - * @method void setLastActivity(int $lastActivity) + * @method void setLastActivity(int $lastactivity) * @method int getLastActivity() */ class DefaultToken extends Entity implements IToken { - /** - * @var string user UID - */ + /** @var string user UID */ protected $uid; - /** - * @var string login name used for generating the token - */ + /** @var string login name used for generating the token */ protected $loginName; - /** - * @var string encrypted user password - */ + /** @var string encrypted user password */ protected $password; - /** - * @var string token name (e.g. browser/OS) - */ + /** @var string token name (e.g. browser/OS) */ protected $name; - /** - * @var string - */ + /** @var string */ protected $token; - /** - * @var int - */ + /** @var int */ protected $type; - /** - * @var int - */ + /** @var int */ protected $remember; - /** - * @var int - */ + /** @var int */ protected $lastActivity; - /** - * @var int - */ + /** @var int */ protected $lastCheck; - /** - * @var string - */ + /** @var string */ protected $scope; + /** @var int */ + protected $expires; + public function __construct() { + $this->addType('uid', 'string'); + $this->addType('loginName', 'string'); + $this->addType('password', 'string'); + $this->addType('name', 'string'); + $this->addType('token', 'string'); $this->addType('type', 'int'); + $this->addType('remember', 'int'); $this->addType('lastActivity', 'int'); $this->addType('lastCheck', 'int'); + $this->addType('scope', 'string'); + $this->addType('expires', 'int'); } - public function getId() { + public function getId(): int { return $this->id; } - public function getUID() { + public function getUID(): string { return $this->uid; } @@ -112,14 +100,14 @@ class DefaultToken extends Entity implements IToken { * * @return string */ - public function getLoginName() { + public function getLoginName(): string { return parent::getLoginName(); } /** * Get the (encrypted) login password * - * @return string + * @return string|null */ public function getPassword() { return parent::getPassword(); @@ -140,7 +128,7 @@ class DefaultToken extends Entity implements IToken { * * @return int */ - public function getLastCheck() { + public function getLastCheck(): int { return parent::getLastCheck(); } @@ -149,15 +137,20 @@ class DefaultToken extends Entity implements IToken { * * @param int $time */ - public function setLastCheck($time) { - return parent::setLastCheck($time); + public function setLastCheck(int $time) { + parent::setLastCheck($time); } - public function getScope() { - return parent::getScope(); + public function getScope(): string { + $scope = parent::getScope(); + if ($scope === null) { + return ''; + } + + return $scope; } - public function getScopeAsArray() { + public function getScopeAsArray(): array { $scope = json_decode($this->getScope(), true); if (!$scope) { return [ @@ -168,10 +161,37 @@ class DefaultToken extends Entity implements IToken { } public function setScope($scope) { - if (is_array($scope)) { + if (\is_array($scope)) { parent::setScope(json_encode($scope)); } else { parent::setScope((string)$scope); } } + + public function getName(): string { + return parent::getName(); + } + + public function getRemember(): int { + return parent::getRemember(); + } + + public function setToken(string $token) { + parent::setToken($token); + } + + public function setPassword(string $password = null) { + parent::setPassword($password); + } + + public function setExpires($expires) { + parent::setExpires($expires); + } + + /** + * @return int|null + */ + public function getExpires() { + return parent::getExpires(); + } } diff --git a/lib/private/Authentication/Token/DefaultTokenMapper.php b/lib/private/Authentication/Token/DefaultTokenMapper.php index 41d1b9f203d..a67d7d151e9 100644 --- a/lib/private/Authentication/Token/DefaultTokenMapper.php +++ b/lib/private/Authentication/Token/DefaultTokenMapper.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -29,12 +30,12 @@ namespace OC\Authentication\Token; use OCP\AppFramework\Db\DoesNotExistException; -use OCP\AppFramework\Db\Mapper; +use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; -class DefaultTokenMapper extends Mapper { +class DefaultTokenMapper extends QBMapper { public function __construct(IDBConnection $db) { parent::__construct($db, 'authtoken'); @@ -45,7 +46,7 @@ class DefaultTokenMapper extends Mapper { * * @param string $token */ - public function invalidate($token) { + public function invalidate(string $token) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -58,7 +59,7 @@ class DefaultTokenMapper extends Mapper { * @param int $olderThan * @param int $remember */ - public function invalidateOld($olderThan, $remember = IToken::DO_NOT_REMEMBER) { + public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -75,10 +76,10 @@ class DefaultTokenMapper extends Mapper { * @throws DoesNotExistException * @return DefaultToken */ - public function getToken($token) { + public function getToken(string $token): DefaultToken { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') + $result = $qb->select('*') ->from('authtoken') ->where($qb->expr()->eq('token', $qb->createNamedParameter($token))) ->execute(); @@ -94,14 +95,14 @@ class DefaultTokenMapper extends Mapper { /** * Get the token for $id * - * @param string $id + * @param int $id * @throws DoesNotExistException * @return DefaultToken */ - public function getTokenById($id) { + public function getTokenById(int $id): DefaultToken { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $result = $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'token', 'last_activity', 'last_check', 'scope') + $result = $qb->select('*') ->from('authtoken') ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))) ->execute(); @@ -123,10 +124,10 @@ class DefaultTokenMapper extends Mapper { * @param IUser $user * @return DefaultToken[] */ - public function getTokenByUser(IUser $user) { + public function getTokenByUser(IUser $user): array { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); - $qb->select('id', 'uid', 'login_name', 'password', 'name', 'type', 'remember', 'token', 'last_activity', 'last_check', 'scope') + $qb->select('*') ->from('authtoken') ->where($qb->expr()->eq('uid', $qb->createNamedParameter($user->getUID()))) ->setMaxResults(1000); @@ -145,7 +146,7 @@ class DefaultTokenMapper extends Mapper { * @param IUser $user * @param int $id */ - public function deleteById(IUser $user, $id) { + public function deleteById(IUser $user, int $id) { /* @var $qb IQueryBuilder */ $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') @@ -159,7 +160,7 @@ class DefaultTokenMapper extends Mapper { * * @param string $name */ - public function deleteByName($name) { + public function deleteByName(string $name) { $qb = $this->db->getQueryBuilder(); $qb->delete('authtoken') ->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR)); diff --git a/lib/private/Authentication/Token/DefaultTokenProvider.php b/lib/private/Authentication/Token/DefaultTokenProvider.php index 36a8b1d5464..5df74cadac4 100644 --- a/lib/private/Authentication/Token/DefaultTokenProvider.php +++ b/lib/private/Authentication/Token/DefaultTokenProvider.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2016, Christoph Wurst <christoph@winzerhof-wurst.at> @@ -85,7 +86,13 @@ class DefaultTokenProvider implements IProvider { * @param int $remember whether the session token should be used for remember-me * @return IToken */ - public function generateToken($token, $uid, $loginName, $password, $name, $type = IToken::TEMPORARY_TOKEN, $remember = IToken::DO_NOT_REMEMBER) { + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken { $dbToken = new DefaultToken(); $dbToken->setUid($uid); $dbToken->setLoginName($loginName); @@ -145,7 +152,7 @@ class DefaultTokenProvider implements IProvider { * @param IUser $user * @return IToken[] */ - public function getTokenByUser(IUser $user) { + public function getTokenByUser(IUser $user): array { return $this->mapper->getTokenByUser($user); } @@ -154,29 +161,43 @@ class DefaultTokenProvider implements IProvider { * * @param string $tokenId * @throws InvalidTokenException - * @return DefaultToken + * @throws ExpiredTokenException + * @return IToken */ - public function getToken($tokenId) { + public function getToken(string $tokenId): IToken { try { - return $this->mapper->getToken($this->hashToken($tokenId)); + $token = $this->mapper->getToken($this->hashToken($tokenId)); } catch (DoesNotExistException $ex) { throw new InvalidTokenException(); } + + if ($token->getExpires() !== null && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + return $token; } /** * Get a token by token id * - * @param string $tokenId + * @param int $tokenId * @throws InvalidTokenException - * @return DefaultToken + * @throws ExpiredTokenException + * @return IToken */ - public function getTokenById($tokenId) { + public function getTokenById(int $tokenId): IToken { try { - return $this->mapper->getTokenById($tokenId); + $token = $this->mapper->getTokenById($tokenId); } catch (DoesNotExistException $ex) { throw new InvalidTokenException(); } + + if ($token->getExpires() !== null && $token->getExpires() < $this->time->getTime()) { + throw new ExpiredTokenException($token); + } + + return $token; } /** @@ -184,7 +205,7 @@ class DefaultTokenProvider implements IProvider { * @param string $sessionId * @throws InvalidTokenException */ - public function renewSessionToken($oldSessionId, $sessionId) { + public function renewSessionToken(string $oldSessionId, string $sessionId) { $token = $this->getToken($oldSessionId); $newToken = new DefaultToken(); @@ -210,7 +231,7 @@ class DefaultTokenProvider implements IProvider { * @throws PasswordlessTokenException * @return string */ - public function getPassword(IToken $savedToken, $tokenId) { + public function getPassword(IToken $savedToken, string $tokenId): string { $password = $savedToken->getPassword(); if (is_null($password)) { throw new PasswordlessTokenException(); @@ -226,7 +247,7 @@ class DefaultTokenProvider implements IProvider { * @param string $password * @throws InvalidTokenException */ - public function setPassword(IToken $token, $tokenId, $password) { + public function setPassword(IToken $token, string $tokenId, string $password) { if (!($token instanceof DefaultToken)) { throw new InvalidTokenException(); } @@ -240,7 +261,7 @@ class DefaultTokenProvider implements IProvider { * * @param string $token */ - public function invalidateToken($token) { + public function invalidateToken(string $token) { $this->mapper->invalidate($this->hashToken($token)); } @@ -250,7 +271,7 @@ class DefaultTokenProvider implements IProvider { * @param IUser $user * @param int $id */ - public function invalidateTokenById(IUser $user, $id) { + public function invalidateTokenById(IUser $user, int $id) { $this->mapper->deleteById($user, $id); } @@ -267,10 +288,32 @@ class DefaultTokenProvider implements IProvider { } /** + * Rotate the token. Usefull for for example oauth tokens + * + * @param IToken $token + * @param string $oldTokenId + * @param string $newTokenId + * @return IToken + */ + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken { + try { + $password = $this->getPassword($token, $oldTokenId); + $token->setPassword($this->encryptPassword($password, $newTokenId)); + } catch (PasswordlessTokenException $e) { + + } + + $token->setToken($this->hashToken($newTokenId)); + $this->updateToken($token); + + return $token; + } + + /** * @param string $token * @return string */ - private function hashToken($token) { + private function hashToken(string $token) { $secret = $this->config->getSystemValue('secret'); return hash('sha512', $token . $secret); } @@ -284,7 +327,7 @@ class DefaultTokenProvider implements IProvider { * @param string $token * @return string encrypted password */ - private function encryptPassword($password, $token) { + private function encryptPassword(string $password, string $token): string { $secret = $this->config->getSystemValue('secret'); return $this->crypto->encrypt($password, $token . $secret); } @@ -299,7 +342,7 @@ class DefaultTokenProvider implements IProvider { * @throws InvalidTokenException * @return string the decrypted key */ - private function decryptPassword($password, $token) { + private function decryptPassword(string $password, string $token): string { $secret = $this->config->getSystemValue('secret'); try { return $this->crypto->decrypt($password, $token . $secret); diff --git a/lib/private/Authentication/Token/IProvider.php b/lib/private/Authentication/Token/IProvider.php index e1cc8182ff0..0efffefac68 100644 --- a/lib/private/Authentication/Token/IProvider.php +++ b/lib/private/Authentication/Token/IProvider.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -44,25 +45,33 @@ interface IProvider { * @param int $remember whether the session token should be used for remember-me * @return IToken */ - public function generateToken($token, $uid, $loginName, $password, $name, $type = IToken::TEMPORARY_TOKEN, $remember = IToken::DO_NOT_REMEMBER); + public function generateToken(string $token, + string $uid, + string $loginName, + $password, + string $name, + int $type = IToken::TEMPORARY_TOKEN, + int $remember = IToken::DO_NOT_REMEMBER): IToken; /** * Get a token by token id * * @param string $tokenId * @throws InvalidTokenException + * @throws ExpiredTokenException * @return IToken */ - public function getToken($tokenId); + public function getToken(string $tokenId): IToken; /** * Get a token by token id * - * @param string $tokenId + * @param int $tokenId * @throws InvalidTokenException - * @return DefaultToken + * @throws ExpiredTokenException + * @return IToken */ - public function getTokenById($tokenId); + public function getTokenById(int $tokenId): IToken; /** * Duplicate an existing session token @@ -71,14 +80,14 @@ interface IProvider { * @param string $sessionId * @throws InvalidTokenException */ - public function renewSessionToken($oldSessionId, $sessionId); + public function renewSessionToken(string $oldSessionId, string $sessionId); /** * Invalidate (delete) the given session token * * @param string $token */ - public function invalidateToken($token); + public function invalidateToken(string $token); /** * Invalidate (delete) the given token @@ -86,7 +95,7 @@ interface IProvider { * @param IUser $user * @param int $id */ - public function invalidateTokenById(IUser $user, $id); + public function invalidateTokenById(IUser $user, int $id); /** * Invalidate (delete) old session tokens @@ -116,7 +125,7 @@ interface IProvider { * @param IUser $user * @return IToken[] */ - public function getTokenByUser(IUser $user); + public function getTokenByUser(IUser $user): array; /** * Get the (unencrypted) password of the given token @@ -127,7 +136,7 @@ interface IProvider { * @throws PasswordlessTokenException * @return string */ - public function getPassword(IToken $token, $tokenId); + public function getPassword(IToken $token, string $tokenId): string; /** * Encrypt and set the password of the given token @@ -137,5 +146,15 @@ interface IProvider { * @param string $password * @throws InvalidTokenException */ - public function setPassword(IToken $token, $tokenId, $password); + public function setPassword(IToken $token, string $tokenId, string $password); + + /** + * Rotate the token. Usefull for for example oauth tokens + * + * @param IToken $token + * @param string $oldTokenId + * @param string $newTokenId + * @return IToken + */ + public function rotate(IToken $token, string $oldTokenId, string $newTokenId): IToken; } diff --git a/lib/private/Authentication/Token/IToken.php b/lib/private/Authentication/Token/IToken.php index a24d31e2ed2..e122ec02764 100644 --- a/lib/private/Authentication/Token/IToken.php +++ b/lib/private/Authentication/Token/IToken.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -37,26 +38,26 @@ interface IToken extends JsonSerializable { * * @return int */ - public function getId(); + public function getId(): int; /** * Get the user UID * * @return string */ - public function getUID(); + public function getUID(): string; /** * Get the login name used when generating the token * * @return string */ - public function getLoginName(); + public function getLoginName(): string; /** * Get the (encrypted) login password * - * @return string + * @return string|null */ public function getPassword(); @@ -65,28 +66,28 @@ interface IToken extends JsonSerializable { * * @return int */ - public function getLastCheck(); + public function getLastCheck(): int; /** * Set the timestamp of the last password check * * @param int $time */ - public function setLastCheck($time); + public function setLastCheck(int $time); /** * Get the authentication scope for this token * * @return string */ - public function getScope(); + public function getScope(): string; /** * Get the authentication scope for this token * * @return array */ - public function getScopeAsArray(); + public function getScopeAsArray(): array; /** * Set the authentication scope for this token @@ -94,4 +95,38 @@ interface IToken extends JsonSerializable { * @param array $scope */ public function setScope($scope); + + /** + * Get the name of the token + * @return string + */ + public function getName(): string; + + /** + * Get the remember state of the token + * + * @return int + */ + public function getRemember(): int; + + /** + * Set the token + * + * @param string $token + */ + public function setToken(string $token); + + /** + * Set the password + * + * @param string $password + */ + public function setPassword(string $password); + + /** + * Set the expiration time of the token + * + * @param int|null $expires + */ + public function setExpires($expires); } diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php index 270e834b8e5..e25bf450446 100644 --- a/lib/private/Files/AppData/AppData.php +++ b/lib/private/Files/AppData/AppData.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> * @@ -31,6 +32,7 @@ use OC\SystemConfig; use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFolder; class AppData implements IAppData { @@ -55,7 +57,7 @@ class AppData implements IAppData { */ public function __construct(IRootFolder $rootFolder, SystemConfig $systemConfig, - $appId) { + string $appId) { $this->rootFolder = $rootFolder; $this->config = $systemConfig; @@ -66,7 +68,7 @@ class AppData implements IAppData { * @return Folder * @throws \RuntimeException */ - private function getAppDataFolder() { + private function getAppDataFolder(): Folder { if ($this->folder === null) { $instanceId = $this->config->getValue('instanceid', null); if ($instanceId === null) { @@ -101,20 +103,20 @@ class AppData implements IAppData { return $this->folder; } - public function getFolder($name) { + public function getFolder(string $name): ISimpleFolder { $node = $this->getAppDataFolder()->get($name); /** @var Folder $node */ return new SimpleFolder($node); } - public function newFolder($name) { + public function newFolder(string $name): ISimpleFolder { $folder = $this->getAppDataFolder()->newFolder($name); return new SimpleFolder($folder); } - public function getDirectoryListing() { + public function getDirectoryListing(): array { $listing = $this->getAppDataFolder()->getDirectoryListing(); $fileListing = array_map(function(Node $folder) { @@ -128,4 +130,8 @@ class AppData implements IAppData { return array_values($fileListing); } + + public function getId(): int { + return $this->getAppDataFolder()->getId(); + } } diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php index 85c75733796..fba2232db06 100644 --- a/lib/private/Files/AppData/Factory.php +++ b/lib/private/Files/AppData/Factory.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> * @@ -44,7 +45,7 @@ class Factory { * @param string $appId * @return AppData */ - public function get($appId) { + public function get(string $appId): AppData { return new AppData($this->rootFolder, $this->config, $appId); } } diff --git a/lib/private/Preview/BackgroundCleanupJob.php b/lib/private/Preview/BackgroundCleanupJob.php new file mode 100644 index 00000000000..25bf354e28b --- /dev/null +++ b/lib/private/Preview/BackgroundCleanupJob.php @@ -0,0 +1,91 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Preview; + +use OC\BackgroundJob\TimedJob; +use OC\Files\AppData\Factory; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IDBConnection; + +class BackgroundCleanupJob extends TimedJob { + + /** @var IDBConnection */ + private $connection; + + /** @var Factory */ + private $appDataFactory; + + /** @var bool */ + private $isCLI; + + public function __construct(IDBConnection $connection, + Factory $appDataFactory, + bool $isCLI) { + // Run at most once an hour + $this->setInterval(3600); + + $this->connection = $connection; + $this->appDataFactory = $appDataFactory; + $this->isCLI = $isCLI; + } + + public function run($argument) { + $previews = $this->appDataFactory->get('preview'); + + $previewFodlerId = $previews->getId(); + + $qb = $this->connection->getQueryBuilder(); + $qb->select('a.name') + ->from('filecache', 'a') + ->leftJoin('a', 'filecache', 'b', $qb->expr()->eq( + $qb->expr()->castColumn('a.name', IQueryBuilder::PARAM_INT), 'b.fileid' + )) + ->where( + $qb->expr()->isNull('b.fileid') + )->andWhere( + $qb->expr()->eq('a.parent', $qb->createNamedParameter($previewFodlerId)) + ); + + if (!$this->isCLI) { + $qb->setMaxResults(10); + } + + $cursor = $qb->execute(); + + while ($row = $cursor->fetch()) { + try { + $preview = $previews->getFolder($row['name']); + $preview->delete(); + } catch (NotFoundException $e) { + // continue + } catch (NotPermittedException $e) { + // continue + } + } + + $cursor->closeCursor(); + } +} diff --git a/lib/private/Preview/Watcher.php b/lib/private/Preview/Watcher.php index 8d091b84b0e..0c0531dff94 100644 --- a/lib/private/Preview/Watcher.php +++ b/lib/private/Preview/Watcher.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * @@ -22,7 +23,6 @@ */ namespace OC\Preview; -use OCP\Files\File; use OCP\Files\Node; use OCP\Files\Folder; use OCP\Files\IAppData; @@ -39,9 +39,6 @@ class Watcher { /** @var IAppData */ private $appData; - /** @var int[] */ - private $toDelete = []; - /** * Watcher constructor. * @@ -52,53 +49,26 @@ class Watcher { } public function postWrite(Node $node) { + $this->deleteNode($node); + } + + protected function deleteNode(Node $node) { // We only handle files if ($node instanceof Folder) { return; } try { - $folder = $this->appData->getFolder($node->getId()); + $folder = $this->appData->getFolder((string)$node->getId()); $folder->delete(); } catch (NotFoundException $e) { //Nothing to do } } - public function preDelete(Node $node) { - // To avoid cycles - if ($this->toDelete !== []) { - return; - } - - if ($node instanceof File) { - $this->toDelete[] = $node->getId(); - return; - } - - /** @var Folder $node */ - $this->deleteFolder($node); - } - - private function deleteFolder(Folder $folder) { - $nodes = $folder->getDirectoryListing(); - foreach ($nodes as $node) { - if ($node instanceof File) { - $this->toDelete[] = $node->getId(); - } else if ($node instanceof Folder) { - $this->deleteFolder($node); - } - } - } - - public function postDelete(Node $node) { - foreach ($this->toDelete as $fid) { - try { - $folder = $this->appData->getFolder($fid); - $folder->delete(); - } catch (NotFoundException $e) { - // continue - } + public function versionRollback(array $data) { + if (isset($data['node'])) { + $this->deleteNode($data['node']); } } } diff --git a/lib/private/Preview/WatcherConnector.php b/lib/private/Preview/WatcherConnector.php index 4e6e786cec7..f374b909d8f 100644 --- a/lib/private/Preview/WatcherConnector.php +++ b/lib/private/Preview/WatcherConnector.php @@ -1,4 +1,5 @@ <?php +declare(strict_types=1); /** * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * @@ -49,7 +50,7 @@ class WatcherConnector { /** * @return Watcher */ - private function getWatcher() { + private function getWatcher(): Watcher { return \OC::$server->query(Watcher::class); } @@ -60,13 +61,7 @@ class WatcherConnector { $this->getWatcher()->postWrite($node); }); - $this->root->listen('\OC\Files', 'preDelete', function (Node $node) { - $this->getWatcher()->preDelete($node); - }); - - $this->root->listen('\OC\Files', 'postDelete', function (Node $node) { - $this->getWatcher()->postDelete($node); - }); + \OC_Hook::connect('\OCP\Versions', 'rollback', $this->getWatcher(), 'versionRollback'); } } } diff --git a/lib/private/Repair.php b/lib/private/Repair.php index a257ef061e7..8746f1e6f27 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -36,6 +36,7 @@ use OC\Repair\Collation; use OC\Repair\MoveUpdaterStepFile; use OC\Repair\NC11\FixMountStorages; use OC\Repair\NC13\AddLogRotateJob; +use OC\Repair\NC14\AddPreviewBackgroundCleanupJob; use OC\Repair\OldGroupMembershipShares; use OC\Repair\Owncloud\DropAccountTermsTable; use OC\Repair\Owncloud\SaveAccountsTableData; @@ -132,7 +133,8 @@ class Repair implements IOutput{ new FixMountStorages(\OC::$server->getDatabaseConnection()), new RepairInvalidPaths(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()), new AddLogRotateJob(\OC::$server->getJobList()), - new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)) + new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OC::$server->query(SCSSCacher::class), \OC::$server->query(JSCombiner::class)), + new AddPreviewBackgroundCleanupJob(\OC::$server->getJobList()), ]; } diff --git a/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php b/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php new file mode 100644 index 00000000000..b58fabcba50 --- /dev/null +++ b/lib/private/Repair/NC14/AddPreviewBackgroundCleanupJob.php @@ -0,0 +1,48 @@ +<?php +declare(strict_types=1); +/** + * @copyright 2018, 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 OC\Repair\NC14; + +use OC\Preview\BackgroundCleanupJob; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class AddPreviewBackgroundCleanupJob implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + public function getName(): string { + return 'Add preview background cleanup job'; + } + + public function run(IOutput $output) { + $this->jobList->add(BackgroundCleanupJob::class); + } + +} diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 5564bb5b072..25e0b4d8817 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -47,6 +47,7 @@ use OC\App\AppStore\Bundles\BundleFetcher; use OC\Authentication\Token\DefaultTokenCleanupJob; use OC\Authentication\Token\DefaultTokenProvider; use OC\Log\Rotate; +use OC\Preview\BackgroundCleanupJob; use OCP\Defaults; use OCP\IL10N; use OCP\ILogger; @@ -419,6 +420,7 @@ class Setup { $jobList = \OC::$server->getJobList(); $jobList->add(DefaultTokenCleanupJob::class); $jobList->add(Rotate::class); + $jobList->add(BackgroundCleanupJob::class); } /** |