diff --git a/lib/private/AvatarManager.php b/lib/private/AvatarManager.php index 0f64f8487a56..184a244412ff 100644 --- a/lib/private/AvatarManager.php +++ b/lib/private/AvatarManager.php @@ -84,15 +84,37 @@ public function getAvatar($userId) { throw new \Exception('user does not exist'); } - /* - * Fix for #22119 - * Basically we do not want to copy the skeleton folder - */ - \OC\Files\Filesystem::initMountPoints($userId); - $dir = '/' . $userId; - /** @var Folder $folder */ - $folder = $this->rootFolder->get($dir); + $avatarsFolder = $this->getAvatarFolder($userId); + return new Avatar($avatarsFolder, $this->l, $user, $this->logger); + } + + private function getFolder(Folder $folder, $path) { + try { + return $folder->get($path); + } catch (NotFoundException $e) { + return $folder->newFolder($path); + } + } + + private function buildAvatarPath($userId) { + $avatar = substr_replace(substr_replace(md5($userId), '/', 4, 0), '/', 2, 0); + return explode('/', $avatar); + } - return new Avatar($folder, $this->l, $user, $this->logger); + /** + * Returns the avatar folder for the given user + * + * @param $userId user id + * @return Folder|\OCP\Files\Node + * + * @internal + */ + public function getAvatarFolder($userId) { + $avatarsFolder = $this->getFolder($this->rootFolder, 'avatars'); + $parts = $this->buildAvatarPath($userId); + foreach ($parts as $part) { + $avatarsFolder = $this->getFolder($avatarsFolder, $part); + } + return $avatarsFolder; } } diff --git a/lib/private/Encryption/Util.php b/lib/private/Encryption/Util.php index 2451b1752364..006ca315200b 100644 --- a/lib/private/Encryption/Util.php +++ b/lib/private/Encryption/Util.php @@ -96,6 +96,9 @@ public function __construct( $this->config = $config; $this->excludedPaths[] = 'files_encryption'; + $this->excludedPaths[] = 'avatars'; + $this->excludedPaths[] = 'avatar.png'; + $this->excludedPaths[] = 'avatar.jpg'; } /** diff --git a/lib/private/Files/Node/LazyRoot.php b/lib/private/Files/Node/LazyRoot.php index f317208f6a10..d2b11fbe1ade 100644 --- a/lib/private/Files/Node/LazyRoot.php +++ b/lib/private/Files/Node/LazyRoot.php @@ -138,7 +138,7 @@ public function unMount($mount) { * @inheritDoc */ public function get($path) { - $this->__call(__FUNCTION__, func_get_args()); + return $this->__call(__FUNCTION__, func_get_args()); } /** diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 036ba13eeebc..31841402276c 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -54,6 +54,7 @@ use OCP\Migration\IRepairStep; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\GenericEvent; +use OC\Repair\MoveAvatarOutsideHome; class Repair implements IOutput{ /* @var IRepairStep[] */ @@ -139,6 +140,15 @@ public static function getRepairSteps() { new SharePropagation(\OC::$server->getConfig()), new RemoveOldShares(\OC::$server->getDatabaseConnection()), new AvatarPermissions(\OC::$server->getDatabaseConnection()), + new MoveAvatarOutsideHome( + \OC::$server->getConfig(), + \OC::$server->getDatabaseConnection(), + \OC::$server->getUserManager(), + \OC::$server->getAvatarManager(), + \OC::$server->getLazyRootFolder(), + \OC::$server->getL10N('core'), + \OC::$server->getLogger() + ), new RemoveRootShares(\OC::$server->getDatabaseConnection(), \OC::$server->getUserManager(), \OC::$server->getLazyRootFolder()), new RepairUnmergedShares( \OC::$server->getConfig(), diff --git a/lib/private/Repair/MoveAvatarOutsideHome.php b/lib/private/Repair/MoveAvatarOutsideHome.php new file mode 100644 index 000000000000..e302d880facf --- /dev/null +++ b/lib/private/Repair/MoveAvatarOutsideHome.php @@ -0,0 +1,163 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Repair; + +use OCP\Files\IRootFolder; +use OCP\IDBConnection; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; +use OCP\IUser; +use OC\Avatar; +use OCP\IConfig; +use OCP\IAvatarManager; +use OCP\Files\NotFoundException; + +/** + * Move avatars outside of their homes to the new location + * + * @package OC\Repair + */ +class MoveAvatarOutsideHome implements IRepairStep { + /** @var \OCP\IConfig */ + protected $config; + + /** @var IDBConnection */ + private $connection; + + /** @var IUserManager */ + private $userManager; + + /** @var IAvatarManager */ + private $avatarManager; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var \OCP\ILogger */ + private $logger; + + /** @var \OCP\IL10N */ + private $l; + + /** + * @param IConfig $config config + * @param IDBConnection $connection database connection + * @param IUserManager $userManager user manager + * @param IAvatarManager $avatarManager + * @param IRootFolder $rootFolder + * @param IL10N $l10n + * @param ILogger $logger + */ + public function __construct( + IConfig $config, + IDBConnection $connection, + IUserManager $userManager, + IAvatarManager $avatarManager, + IRootFolder $rootFolder, + IL10N $l10n, + ILogger $logger + ) { + $this->config = $config; + $this->connection = $connection; + $this->userManager = $userManager; + $this->avatarManager = $avatarManager; + $this->rootFolder = $rootFolder; + $this->l = $l10n; + $this->logger = $logger; + } + + /** + * @return string + */ + public function getName() { + return 'Move user avatars outside the homes to the new location'; + } + + /** + * Move avatars outside of their homes + * + * @param IOutput $out + * @param IUser $user + */ + private function moveAvatars(IOutput $out, IUser $user) { + $userId = $user->getUID(); + + \OC\Files\Filesystem::initMountPoints($userId); + + // call get instead of getUserFolder to avoid needless skeleton copy + try { + $oldAvatarUserFolder = $this->rootFolder->get('/' . $userId); + $oldAvatar = new Avatar($oldAvatarUserFolder, $this->l, $user, $this->logger); + if ($oldAvatar->exists()) { + $newAvatarsUserFolder = $this->avatarManager->getAvatarFolder($userId); + + // get original file + $oldAvatarFile = $oldAvatar->getFile(-1); + $oldAvatarFile->move($newAvatarsUserFolder->getPath() . '/' . $oldAvatarFile->getName()); + $oldAvatar->remove(); + } + } catch (NotFoundException $e) { + // not all users have a home, ignore + } + + \OC_Util::tearDownFS(); + } + + /** + * Count all the users + * + * @return int + */ + private function countUsers() { + $allCount = $this->userManager->countUsers(); + + $totalCount = 0; + foreach ($allCount as $backend => $count) { + $totalCount += $count; + } + + return $totalCount; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + if (version_compare($ocVersionFromBeforeUpdate, '9.2.0.2', '<')) { + $function = function(IUser $user) use ($output) { + $this->moveAvatars($output, $user); + $output->advance(); + }; + + $userCount = $this->countUsers(); + $output->startProgress($userCount); + + $this->userManager->callForAllUsers($function); + + $output->finishProgress(); + } + } +} + diff --git a/tests/lib/AvatarManagerTest.php b/tests/lib/AvatarManagerTest.php index 2dd6ff349230..4d1ce17ccdbd 100644 --- a/tests/lib/AvatarManagerTest.php +++ b/tests/lib/AvatarManagerTest.php @@ -22,32 +22,43 @@ namespace Test; use OC\AvatarManager; -use Test\Traits\UserTrait; -use Test\Traits\MountProviderTrait; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IL10N; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; /** * Class AvatarManagerTest - * @group DB */ -class AvatarManagerTest extends \Test\TestCase { - use UserTrait; - use MountProviderTrait; +class AvatarManagerTest extends TestCase { - /** @var AvatarManager */ + /** @var AvatarManager | \PHPUnit_Framework_MockObject_MockObject */ private $avatarManager; - /** @var \OC\Files\Storage\Temporary */ - private $storage; + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + + /** @var IRootFolder | \PHPUnit_Framework_MockObject_MockObject */ + private $rootFolder; public function setUp() { parent::setUp(); - $this->createUser('valid-user', 'valid-user'); + $this->userManager = $this->createMock(IUserManager::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $l = $this->createMock(IL10N::class); + $logger = $this->createMock(ILogger::class); - $this->storage = new \OC\Files\Storage\Temporary(); - $this->registerMount('valid-user', $this->storage, '/valid-user/'); - $this->avatarManager = \OC::$server->getAvatarManager(); + $this->avatarManager = $this->getMockBuilder(AvatarManager::class) + ->setMethods(['getAvatarFolder']) + ->setConstructorArgs([$this->userManager, + $this->rootFolder, + $l, + $logger]) + ->getMock(); } /** @@ -59,10 +70,31 @@ public function testGetAvatarInvalidUser() { } public function testGetAvatarValidUser() { + $user = $this->createMock(IUser::class); + $this->userManager->expects($this->once())->method('get')->willReturn($user); + + $folder = $this->createMock(Folder::class); + $this->avatarManager->expects($this->once())->method('getAvatarFolder')->willReturn($folder); + $avatar = $this->avatarManager->getAvatar('valid-user'); $this->assertInstanceOf('\OCP\IAvatar', $avatar); - $this->assertFalse($this->storage->file_exists('files')); } + /** + * @dataProvider providesUserIds + */ + public function testPathBuilding($expectedPath, $userId) { + $path = $this->invokePrivate($this->avatarManager, 'buildAvatarPath', [$userId]); + $this->assertEquals($expectedPath, implode('/', $path)); + } + + public function providesUserIds() { + return [ + ['21/23/2f297a57a5a743894a0e4a801fc3', 'admin'], + ['c4/ca/4238a0b923820dcc509a6f75849b', '1'], + ['f9/5b/70fdc3088560732a5ac135644506', '{'], + ['d4/1d/8cd98f00b204e9800998ecf8427e', ''], + ]; + } } diff --git a/version.php b/version.php index 099b51a8997d..0d61a3c3e97c 100644 --- a/version.php +++ b/version.php @@ -23,7 +23,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(9, 2, 0, 1); +$OC_Version = array(9, 2, 0, 2); // The human readable string $OC_VersionString = '9.2.0 pre alpha';