mirror of
https://github.com/nextcloud/server.git
synced 2026-02-18 18:28:50 -05:00
Revert "perf(base): Stop setting up the FS for every basic auth request"
This commit is contained in:
parent
cf3ffb3fd1
commit
2b50d9b2c5
24 changed files with 937 additions and 51 deletions
|
|
@ -6,7 +6,6 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
// Backends
|
||||
use OC\Files\SetupManager;
|
||||
use OC\KnownUser\KnownUserService;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarRoot;
|
||||
|
|
@ -41,7 +40,6 @@ $authBackend = new Auth(
|
|||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(SetupManager::class),
|
||||
'principals/'
|
||||
);
|
||||
$principalBackend = new Principal(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
// Backends
|
||||
use OC\Files\SetupManager;
|
||||
use OC\KnownUser\KnownUserService;
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\CalDAV\Proxy\ProxyMapper;
|
||||
|
|
@ -42,7 +41,6 @@ $authBackend = new Auth(
|
|||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(SetupManager::class),
|
||||
'principals/'
|
||||
);
|
||||
$principalBackend = new Principal(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\SetupManager;
|
||||
use OCA\DAV\Connector\Sabre\Auth;
|
||||
use OCA\DAV\Connector\Sabre\BearerAuth;
|
||||
use OCA\DAV\Connector\Sabre\ServerFactory;
|
||||
|
|
@ -56,7 +55,6 @@ $authBackend = new Auth(
|
|||
Server::get(IRequest::class),
|
||||
Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
Server::get(IThrottler::class),
|
||||
Server::get(SetupManager::class),
|
||||
'principals/'
|
||||
);
|
||||
$authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ namespace OCA\DAV\Connector\Sabre;
|
|||
use Exception;
|
||||
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
|
||||
use OC\Authentication\TwoFactorAuth\Manager;
|
||||
use OC\Files\SetupManager;
|
||||
use OC\User\Session;
|
||||
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
|
||||
use OCA\DAV\Connector\Sabre\Exception\TooManyRequests;
|
||||
|
|
@ -38,7 +37,6 @@ class Auth extends AbstractBasic {
|
|||
private IRequest $request,
|
||||
private Manager $twoFactorManager,
|
||||
private IThrottler $throttler,
|
||||
private SetupManager $setupManager,
|
||||
string $principalPrefix = 'principals/users/',
|
||||
) {
|
||||
$this->principalPrefix = $principalPrefix;
|
||||
|
|
@ -185,13 +183,10 @@ class Auth extends AbstractBasic {
|
|||
|| ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && empty($request->getHeader('Authorization')))
|
||||
|| \OC_User::handleApacheAuth()
|
||||
) {
|
||||
$user = $this->userSession->getUser();
|
||||
$this->setupManager->setupForUser($user);
|
||||
|
||||
$uid = $user->getUID();
|
||||
$this->currentUser = $uid;
|
||||
$user = $this->userSession->getUser()->getUID();
|
||||
$this->currentUser = $user;
|
||||
$this->session->close();
|
||||
return [true, $this->principalPrefix . $uid];
|
||||
return [true, $this->principalPrefix . $user];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -206,12 +201,6 @@ class Auth extends AbstractBasic {
|
|||
$response->setStatus(Http::STATUS_UNAUTHORIZED);
|
||||
throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
|
||||
}
|
||||
|
||||
$user = $this->userSession->getUser();
|
||||
if ($user !== null) {
|
||||
$this->setupManager->setupForUser($user);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
namespace OCA\DAV;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\SetupManager;
|
||||
use OCA\DAV\AppInfo\PluginManager;
|
||||
use OCA\DAV\BulkUpload\BulkUploadPlugin;
|
||||
use OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin;
|
||||
|
|
@ -133,8 +132,7 @@ class Server {
|
|||
\OCP\Server::get(IUserSession::class),
|
||||
\OCP\Server::get(IRequest::class),
|
||||
\OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
|
||||
\OCP\Server::get(IThrottler::class),
|
||||
\OCP\Server::get(SetupManager::class),
|
||||
\OCP\Server::get(IThrottler::class)
|
||||
);
|
||||
|
||||
// Set URL explicitly due to reverse-proxy situations
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre;
|
|||
|
||||
use OC\Authentication\Exceptions\PasswordLoginForbiddenException;
|
||||
use OC\Authentication\TwoFactorAuth\Manager;
|
||||
use OC\Files\SetupManager;
|
||||
use OC\User\Session;
|
||||
use OCA\DAV\Connector\Sabre\Auth;
|
||||
use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden;
|
||||
|
|
@ -36,7 +35,6 @@ class AuthTest extends TestCase {
|
|||
private IRequest&MockObject $request;
|
||||
private Manager&MockObject $twoFactorManager;
|
||||
private IThrottler&MockObject $throttler;
|
||||
private SetupManager&MockObject $setupManager;
|
||||
private Auth $auth;
|
||||
|
||||
protected function setUp(): void {
|
||||
|
|
@ -46,14 +44,12 @@ class AuthTest extends TestCase {
|
|||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->twoFactorManager = $this->createMock(Manager::class);
|
||||
$this->throttler = $this->createMock(IThrottler::class);
|
||||
$this->setupManager = $this->createMock(SetupManager::class);
|
||||
$this->auth = new Auth(
|
||||
$this->session,
|
||||
$this->userSession,
|
||||
$this->request,
|
||||
$this->twoFactorManager,
|
||||
$this->throttler,
|
||||
$this->setupManager,
|
||||
$this->throttler
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -583,7 +579,7 @@ class AuthTest extends TestCase {
|
|||
->method('getUID')
|
||||
->willReturn('MyTestUser');
|
||||
$this->userSession
|
||||
->expects($this->exactly(4))
|
||||
->expects($this->exactly(3))
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
$response = $this->auth->check($server->httpRequest, $server->httpResponse);
|
||||
|
|
|
|||
|
|
@ -182,7 +182,8 @@ export default {
|
|||
if (data.status === 'success') {
|
||||
this.handleAvatarUpdate(false)
|
||||
} else if (data.data === 'notsquare') {
|
||||
this.$refs.cropper.replace(data.image)
|
||||
const tempAvatar = generateUrl('/avatar/tmp') + '?requesttoken=' + encodeURIComponent(OC.requestToken) + '#' + Math.floor(Math.random() * 1000)
|
||||
this.$refs.cropper.replace(tempAvatar)
|
||||
this.showCropper = true
|
||||
} else {
|
||||
showError(data.data.message)
|
||||
|
|
|
|||
BIN
build/integration/data/coloured-pattern-non-square.png
Normal file
BIN
build/integration/data/coloured-pattern-non-square.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,2 @@
|
|||
SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
|
@ -21,9 +21,32 @@ Feature: avatar
|
|||
And last avatar is a square of size 512
|
||||
And last avatar is not a single color
|
||||
|
||||
|
||||
|
||||
Scenario: get temporary non-square user avatar before cropping it
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
When logged in user gets temporary avatar
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
# "last avatar" also includes the last temporary avatar
|
||||
And last avatar is not a square
|
||||
And last avatar is not a single color
|
||||
|
||||
Scenario: get non-square user avatar before cropping it
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
# Avatar needs to be cropped to finish setting it
|
||||
When user "user0" gets avatar for user "user0"
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 0 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is not a single color
|
||||
|
||||
Scenario: set square user avatar from file
|
||||
Given Logging in using web as "user0"
|
||||
When logged in user posts avatar from file "data/green-square-256.png"
|
||||
When logged in user posts temporary avatar from file "data/green-square-256.png"
|
||||
And user "user0" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
|
|
@ -41,7 +64,7 @@ Feature: avatar
|
|||
Scenario: set square user avatar from internal path
|
||||
Given user "user0" uploads file "data/green-square-256.png" to "/internal-green-square-256.png"
|
||||
And Logging in using web as "user0"
|
||||
When logged in user posts avatar from internal path "internal-green-square-256.png"
|
||||
When logged in user posts temporary avatar from internal path "internal-green-square-256.png"
|
||||
And user "user0" gets avatar for user "user0" with size "64"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
|
|
@ -55,21 +78,82 @@ Feature: avatar
|
|||
And last avatar is a square of size 64
|
||||
And last avatar is a single "#00FF00" color
|
||||
|
||||
Scenario: delete user avatar
|
||||
Scenario: set non-square user avatar from file
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts avatar from file "data/green-square-256.png"
|
||||
When logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
| w | 128 |
|
||||
| h | 128 |
|
||||
Then logged in user gets temporary avatar with 404
|
||||
And user "user0" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#00FF00" color
|
||||
And last avatar is a single "#FF0000" color
|
||||
And user "anonymous" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
|
||||
Scenario: set non-square user avatar from internal path
|
||||
Given user "user0" uploads file "data/coloured-pattern-non-square.png" to "/internal-coloured-pattern-non-square.png"
|
||||
And Logging in using web as "user0"
|
||||
When logged in user posts temporary avatar from internal path "internal-coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 704 |
|
||||
| y | 320 |
|
||||
| w | 64 |
|
||||
| h | 64 |
|
||||
Then logged in user gets temporary avatar with 404
|
||||
And user "user0" gets avatar for user "user0" with size "64"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 64
|
||||
And last avatar is a single "#00FF00" color
|
||||
And user "anonymous" gets avatar for user "user0" with size "64"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 64
|
||||
And last avatar is a single "#00FF00" color
|
||||
|
||||
Scenario: cropped user avatar needs to be squared
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
When logged in user crops temporary avatar with 400
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
| w | 192 |
|
||||
| h | 128 |
|
||||
|
||||
|
||||
|
||||
Scenario: delete user avatar
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
| w | 128 |
|
||||
| h | 128 |
|
||||
And user "user0" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
And user "anonymous" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
When logged in user deletes the user avatar
|
||||
Then user "user0" gets avatar for user "user0"
|
||||
And The following headers should be set
|
||||
|
|
@ -84,6 +168,40 @@ Feature: avatar
|
|||
And last avatar is a square of size 512
|
||||
And last avatar is not a single color
|
||||
|
||||
|
||||
|
||||
Scenario: get user avatar with a larger size than the original one
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
| w | 128 |
|
||||
| h | 128 |
|
||||
When user "user0" gets avatar for user "user0" with size "192"
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
|
||||
Scenario: get user avatar with a smaller size than the original one
|
||||
Given Logging in using web as "user0"
|
||||
And logged in user posts temporary avatar from file "data/coloured-pattern-non-square.png"
|
||||
And logged in user crops temporary avatar
|
||||
| x | 384 |
|
||||
| y | 256 |
|
||||
| w | 128 |
|
||||
| h | 128 |
|
||||
When user "user0" gets avatar for user "user0" with size "96"
|
||||
Then The following headers should be set
|
||||
| Content-Type | image/png |
|
||||
| X-NC-IsCustomAvatar | 1 |
|
||||
And last avatar is a square of size 512
|
||||
And last avatar is a single "#FF0000" color
|
||||
|
||||
|
||||
|
||||
Scenario: get default guest avatar
|
||||
When user "user0" gets avatar for guest "guest0"
|
||||
Then The following headers should be set
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
require __DIR__ . '/../../vendor/autoload.php';
|
||||
|
|
@ -67,11 +68,30 @@ trait Avatar {
|
|||
}
|
||||
|
||||
/**
|
||||
* @When logged in user posts avatar from file :source
|
||||
* @When logged in user gets temporary avatar
|
||||
*/
|
||||
public function loggedInUserGetsTemporaryAvatar() {
|
||||
$this->loggedInUserGetsTemporaryAvatarWith('200');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When logged in user gets temporary avatar with :statusCode
|
||||
*
|
||||
* @param string $statusCode
|
||||
*/
|
||||
public function loggedInUserGetsTemporaryAvatarWith(string $statusCode) {
|
||||
$this->sendingAToWithRequesttoken('GET', '/index.php/avatar/tmp');
|
||||
$this->theHTTPStatusCodeShouldBe($statusCode);
|
||||
|
||||
$this->getLastAvatar();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When logged in user posts temporary avatar from file :source
|
||||
*
|
||||
* @param string $source
|
||||
*/
|
||||
public function loggedInUserPostsAvatarFromFile(string $source) {
|
||||
public function loggedInUserPostsTemporaryAvatarFromFile(string $source) {
|
||||
$file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
|
||||
|
||||
$this->sendingAToWithRequesttoken('POST', '/index.php/avatar',
|
||||
|
|
@ -87,15 +107,40 @@ trait Avatar {
|
|||
}
|
||||
|
||||
/**
|
||||
* @When logged in user posts avatar from internal path :path
|
||||
* @When logged in user posts temporary avatar from internal path :path
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function loggedInUserPostsAvatarFromInternalPath(string $path) {
|
||||
public function loggedInUserPostsTemporaryAvatarFromInternalPath(string $path) {
|
||||
$this->sendingAToWithRequesttoken('POST', '/index.php/avatar?path=' . $path);
|
||||
$this->theHTTPStatusCodeShouldBe('200');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When logged in user crops temporary avatar
|
||||
*
|
||||
* @param TableNode $crop
|
||||
*/
|
||||
public function loggedInUserCropsTemporaryAvatar(TableNode $crop) {
|
||||
$this->loggedInUserCropsTemporaryAvatarWith('200', $crop);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When logged in user crops temporary avatar with :statusCode
|
||||
*
|
||||
* @param string $statusCode
|
||||
* @param TableNode $crop
|
||||
*/
|
||||
public function loggedInUserCropsTemporaryAvatarWith(string $statusCode, TableNode $crop) {
|
||||
$parameters = [];
|
||||
foreach ($crop->getRowsHash() as $key => $value) {
|
||||
$parameters[] = 'crop[' . $key . ']=' . $value;
|
||||
}
|
||||
|
||||
$this->sendingAToWithRequesttoken('POST', '/index.php/avatar/cropped?' . implode('&', $parameters));
|
||||
$this->theHTTPStatusCodeShouldBe($statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When logged in user deletes the user avatar
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3455,6 +3455,17 @@
|
|||
<code><![CDATA[$this->providers]]></code>
|
||||
</UndefinedInterfaceMethod>
|
||||
</file>
|
||||
<file src="lib/private/Cache/CappedMemoryCache.php">
|
||||
<MissingTemplateParam>
|
||||
<code><![CDATA[\ArrayAccess]]></code>
|
||||
</MissingTemplateParam>
|
||||
</file>
|
||||
<file src="lib/private/Cache/File.php">
|
||||
<LessSpecificImplementedReturnType>
|
||||
<code><![CDATA[bool|mixed]]></code>
|
||||
<code><![CDATA[bool|mixed]]></code>
|
||||
</LessSpecificImplementedReturnType>
|
||||
</file>
|
||||
<file src="lib/private/Calendar/Manager.php">
|
||||
<LessSpecificReturnStatement>
|
||||
<code><![CDATA[array_merge(
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
namespace OC\Core\Controller;
|
||||
|
||||
use OC\AppFramework\Utility\TimeFactory;
|
||||
use OC\NotSquareException;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
|
||||
|
|
@ -15,6 +16,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
|||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
|
|
@ -22,6 +24,7 @@ use OCP\Files\File;
|
|||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\IAvatarManager;
|
||||
use OCP\ICache;
|
||||
use OCP\IL10N;
|
||||
use OCP\Image;
|
||||
use OCP\IRequest;
|
||||
|
|
@ -38,6 +41,7 @@ class AvatarController extends Controller {
|
|||
string $appName,
|
||||
IRequest $request,
|
||||
protected IAvatarManager $avatarManager,
|
||||
protected ICache $cache,
|
||||
protected IL10N $l10n,
|
||||
protected IUserManager $userManager,
|
||||
protected IRootFolder $rootFolder,
|
||||
|
|
@ -198,7 +202,8 @@ class AvatarController extends Controller {
|
|||
Http::STATUS_BAD_REQUEST
|
||||
);
|
||||
}
|
||||
$content = file_get_contents($files['tmp_name'][0]);
|
||||
$this->cache->set('avatar_upload', file_get_contents($files['tmp_name'][0]), 7200);
|
||||
$content = $this->cache->get('avatar_upload');
|
||||
unlink($files['tmp_name'][0]);
|
||||
} else {
|
||||
$phpFileUploadErrors = [
|
||||
|
|
@ -245,6 +250,8 @@ class AvatarController extends Controller {
|
|||
try {
|
||||
$avatar = $this->avatarManager->getAvatar($this->userId);
|
||||
$avatar->set($image);
|
||||
// Clean up
|
||||
$this->cache->remove('tmpAvatar');
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'core']);
|
||||
|
|
@ -252,8 +259,9 @@ class AvatarController extends Controller {
|
|||
}
|
||||
}
|
||||
|
||||
$this->cache->set('tmpAvatar', $image->data(), 7200);
|
||||
return new JSONResponse(
|
||||
['data' => 'notsquare', 'image' => 'data:' . $mimeType . ';base64,' . base64_encode($image->data())],
|
||||
['data' => 'notsquare'],
|
||||
Http::STATUS_OK
|
||||
);
|
||||
} else {
|
||||
|
|
@ -280,4 +288,71 @@ class AvatarController extends Controller {
|
|||
return new JSONResponse(['data' => ['message' => $this->l10n->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JSONResponse|DataDisplayResponse
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/avatar/tmp')]
|
||||
public function getTmpAvatar() {
|
||||
$tmpAvatar = $this->cache->get('tmpAvatar');
|
||||
if (is_null($tmpAvatar)) {
|
||||
return new JSONResponse(['data' => [
|
||||
'message' => $this->l10n->t('No temporary profile picture available, try again')
|
||||
]],
|
||||
Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
$image = new Image();
|
||||
$image->loadFromData($tmpAvatar);
|
||||
|
||||
$resp = new DataDisplayResponse(
|
||||
$image->data() ?? '',
|
||||
Http::STATUS_OK,
|
||||
['Content-Type' => $image->mimeType()]);
|
||||
|
||||
$resp->setETag((string)crc32($image->data() ?? ''));
|
||||
$resp->cacheFor(0);
|
||||
$resp->setLastModified(new \DateTime('now', new \DateTimeZone('GMT')));
|
||||
return $resp;
|
||||
}
|
||||
|
||||
#[NoAdminRequired]
|
||||
#[FrontpageRoute(verb: 'POST', url: '/avatar/cropped')]
|
||||
public function postCroppedAvatar(?array $crop = null): JSONResponse {
|
||||
if (is_null($crop)) {
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('No crop data provided')]],
|
||||
Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!isset($crop['x'], $crop['y'], $crop['w'], $crop['h'])) {
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('No valid crop data provided')]],
|
||||
Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$tmpAvatar = $this->cache->get('tmpAvatar');
|
||||
if (is_null($tmpAvatar)) {
|
||||
return new JSONResponse(['data' => [
|
||||
'message' => $this->l10n->t('No temporary profile picture available, try again')
|
||||
]],
|
||||
Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$image = new Image();
|
||||
$image->loadFromData($tmpAvatar);
|
||||
$image->crop($crop['x'], $crop['y'], (int)round($crop['w']), (int)round($crop['h']));
|
||||
try {
|
||||
$avatar = $this->avatarManager->getAvatar($this->userId);
|
||||
$avatar->set($image);
|
||||
// Clean up
|
||||
$this->cache->remove('tmpAvatar');
|
||||
return new JSONResponse(['status' => 'success']);
|
||||
} catch (NotSquareException $e) {
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('Crop is not square')]],
|
||||
Http::STATUS_BAD_REQUEST);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'core']);
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('An error occurred. Please contact your admin.')]], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
dist/settings-vue-settings-personal-info.js
vendored
4
dist/settings-vue-settings-personal-info.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
17
lib/base.php
17
lib/base.php
|
|
@ -883,6 +883,23 @@ class OC {
|
|||
$throttler = Server::get(IThrottler::class);
|
||||
$throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
|
||||
}
|
||||
|
||||
try {
|
||||
$cache = new \OC\Cache\File();
|
||||
$cache->gc();
|
||||
} catch (\OC\ServerNotAvailableException $e) {
|
||||
// not a GC exception, pass it on
|
||||
throw $e;
|
||||
} catch (\OC\ForbiddenException $e) {
|
||||
// filesystem blocked for this request, ignore
|
||||
} catch (\Exception $e) {
|
||||
// a GC exception should not prevent users from using OC,
|
||||
// so log the exception
|
||||
Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
|
||||
'app' => 'core',
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1160,6 +1160,8 @@ return array(
|
|||
'OC\\BinaryFinder' => $baseDir . '/lib/private/BinaryFinder.php',
|
||||
'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => $baseDir . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
|
||||
'OC\\Broadcast\\Events\\BroadcastEvent' => $baseDir . '/lib/private/Broadcast/Events/BroadcastEvent.php',
|
||||
'OC\\Cache\\CappedMemoryCache' => $baseDir . '/lib/private/Cache/CappedMemoryCache.php',
|
||||
'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php',
|
||||
'OC\\Calendar\\AvailabilityResult' => $baseDir . '/lib/private/Calendar/AvailabilityResult.php',
|
||||
'OC\\Calendar\\CalendarEventBuilder' => $baseDir . '/lib/private/Calendar/CalendarEventBuilder.php',
|
||||
'OC\\Calendar\\CalendarQuery' => $baseDir . '/lib/private/Calendar/CalendarQuery.php',
|
||||
|
|
|
|||
|
|
@ -1201,6 +1201,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\BinaryFinder' => __DIR__ . '/../../..' . '/lib/private/BinaryFinder.php',
|
||||
'OC\\Blurhash\\Listener\\GenerateBlurhashMetadata' => __DIR__ . '/../../..' . '/lib/private/Blurhash/Listener/GenerateBlurhashMetadata.php',
|
||||
'OC\\Broadcast\\Events\\BroadcastEvent' => __DIR__ . '/../../..' . '/lib/private/Broadcast/Events/BroadcastEvent.php',
|
||||
'OC\\Cache\\CappedMemoryCache' => __DIR__ . '/../../..' . '/lib/private/Cache/CappedMemoryCache.php',
|
||||
'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php',
|
||||
'OC\\Calendar\\AvailabilityResult' => __DIR__ . '/../../..' . '/lib/private/Calendar/AvailabilityResult.php',
|
||||
'OC\\Calendar\\CalendarEventBuilder' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarEventBuilder.php',
|
||||
'OC\\Calendar\\CalendarQuery' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarQuery.php',
|
||||
|
|
|
|||
109
lib/private/Cache/CappedMemoryCache.php
Normal file
109
lib/private/Cache/CappedMemoryCache.php
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OC\Cache;
|
||||
|
||||
use OCP\ICache;
|
||||
|
||||
/**
|
||||
* In-memory cache with a capacity limit to keep memory usage in check
|
||||
*
|
||||
* Uses a simple FIFO expiry mechanism
|
||||
* @template T
|
||||
* @deprecated 25.0.0 use OCP\Cache\CappedMemoryCache instead
|
||||
*/
|
||||
class CappedMemoryCache implements ICache, \ArrayAccess {
|
||||
private $capacity;
|
||||
/** @var T[] */
|
||||
private $cache = [];
|
||||
|
||||
public function __construct($capacity = 512) {
|
||||
$this->capacity = $capacity;
|
||||
}
|
||||
|
||||
public function hasKey($key): bool {
|
||||
return isset($this->cache[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?T
|
||||
*/
|
||||
public function get($key) {
|
||||
return $this->cache[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param T $value
|
||||
* @param int $ttl
|
||||
* @return bool
|
||||
*/
|
||||
public function set($key, $value, $ttl = 0): bool {
|
||||
if (is_null($key)) {
|
||||
$this->cache[] = $value;
|
||||
} else {
|
||||
$this->cache[$key] = $value;
|
||||
}
|
||||
$this->garbageCollect();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function remove($key) {
|
||||
unset($this->cache[$key]);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function clear($prefix = '') {
|
||||
$this->cache = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function offsetExists($offset): bool {
|
||||
return $this->hasKey($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function &offsetGet($offset) {
|
||||
return $this->cache[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $offset
|
||||
* @param T $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet($offset, $value): void {
|
||||
$this->set($offset, $value);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset): void {
|
||||
$this->remove($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T[]
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
|
||||
private function garbageCollect() {
|
||||
while (count($this->cache) > $this->capacity) {
|
||||
reset($this->cache);
|
||||
$key = key($this->cache);
|
||||
$this->remove($key);
|
||||
}
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
190
lib/private/Cache/File.php
Normal file
190
lib/private/Cache/File.php
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OC\Cache;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\View;
|
||||
use OCP\ICache;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\ISecureRandom;
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class File implements ICache {
|
||||
/** @var View */
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Returns the cache storage for the logged in user
|
||||
*
|
||||
* @return \OC\Files\View cache storage
|
||||
* @throws \OC\ForbiddenException
|
||||
* @throws \OC\User\NoUserException
|
||||
*/
|
||||
protected function getStorage() {
|
||||
if ($this->storage !== null) {
|
||||
return $this->storage;
|
||||
}
|
||||
$session = Server::get(IUserSession::class);
|
||||
if ($session->isLoggedIn()) {
|
||||
$rootView = new View();
|
||||
$userId = $session->getUser()->getUID();
|
||||
Filesystem::initMountPoints($userId);
|
||||
if (!$rootView->file_exists('/' . $userId . '/cache')) {
|
||||
$rootView->mkdir('/' . $userId . '/cache');
|
||||
}
|
||||
$this->storage = new View('/' . $userId . '/cache');
|
||||
return $this->storage;
|
||||
} else {
|
||||
Server::get(LoggerInterface::class)->error('Can\'t get cache storage, user not logged in', ['app' => 'core']);
|
||||
throw new \OC\ForbiddenException('Can\t get cache storage, user not logged in');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function get($key) {
|
||||
$result = null;
|
||||
if ($this->hasKey($key)) {
|
||||
$storage = $this->getStorage();
|
||||
$result = $storage->file_get_contents($key);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the stored/cached data
|
||||
*
|
||||
* @param string $key
|
||||
* @return int
|
||||
*/
|
||||
public function size($key) {
|
||||
$result = 0;
|
||||
if ($this->hasKey($key)) {
|
||||
$storage = $this->getStorage();
|
||||
$result = $storage->filesize($key);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int $ttl
|
||||
* @return bool|mixed
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function set($key, $value, $ttl = 0) {
|
||||
$storage = $this->getStorage();
|
||||
$result = false;
|
||||
// unique id to avoid chunk collision, just in case
|
||||
$uniqueId = Server::get(ISecureRandom::class)->generate(
|
||||
16,
|
||||
ISecureRandom::CHAR_ALPHANUMERIC
|
||||
);
|
||||
|
||||
// use part file to prevent hasKey() to find the key
|
||||
// while it is being written
|
||||
$keyPart = $key . '.' . $uniqueId . '.part';
|
||||
if ($storage && $storage->file_put_contents($keyPart, $value)) {
|
||||
if ($ttl === 0) {
|
||||
$ttl = 86400; // 60*60*24
|
||||
}
|
||||
$result = $storage->touch($keyPart, time() + $ttl);
|
||||
$result &= $storage->rename($keyPart, $key);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function hasKey($key) {
|
||||
$storage = $this->getStorage();
|
||||
if ($storage && $storage->is_file($key) && $storage->isReadable($key)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool|mixed
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function remove($key) {
|
||||
$storage = $this->getStorage();
|
||||
if (!$storage) {
|
||||
return false;
|
||||
}
|
||||
return $storage->unlink($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prefix
|
||||
* @return bool
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function clear($prefix = '') {
|
||||
$storage = $this->getStorage();
|
||||
if ($storage && $storage->is_dir('/')) {
|
||||
$dh = $storage->opendir('/');
|
||||
if (is_resource($dh)) {
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if ($file !== '.' && $file !== '..' && ($prefix === '' || str_starts_with($file, $prefix))) {
|
||||
$storage->unlink('/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs GC
|
||||
* @throws \OC\ForbiddenException
|
||||
*/
|
||||
public function gc() {
|
||||
$storage = $this->getStorage();
|
||||
if ($storage) {
|
||||
// extra hour safety, in case of stray part chunks that take longer to write,
|
||||
// because touch() is only called after the chunk was finished
|
||||
$now = time() - 3600;
|
||||
$dh = $storage->opendir('/');
|
||||
if (!is_resource($dh)) {
|
||||
return null;
|
||||
}
|
||||
while (($file = readdir($dh)) !== false) {
|
||||
if ($file !== '.' && $file !== '..') {
|
||||
try {
|
||||
$mtime = $storage->filemtime('/' . $file);
|
||||
if ($mtime < $now) {
|
||||
$storage->unlink('/' . $file);
|
||||
}
|
||||
} catch (\OCP\Lock\LockedException $e) {
|
||||
// ignore locked chunks
|
||||
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
|
||||
} catch (\OCP\Files\ForbiddenException $e) {
|
||||
Server::get(LoggerInterface::class)->debug('Could not cleanup forbidden chunk "' . $file . '"', ['app' => 'core']);
|
||||
} catch (\OCP\Files\LockNotAcquiredException $e) {
|
||||
Server::get(LoggerInterface::class)->debug('Could not cleanup locked chunk "' . $file . '"', ['app' => 'core']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function isAvailable(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -585,13 +585,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->registerAlias(IURLGenerator::class, URLGenerator::class);
|
||||
|
||||
$this->registerService(ICache::class, function ($c) {
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = $c->get(LoggerInterface::class);
|
||||
$logger->debug('The requested service "' . ICache::class . '" is deprecated. Please use "' . ICacheFactory::class . '" instead to create a cache. This service will be removed in a future Nextcloud version.', ['app' => 'serverDI']);
|
||||
|
||||
/** @var ICacheFactory $cacheFactory */
|
||||
$cacheFactory = $c->get(ICacheFactory::class);
|
||||
return $cacheFactory->createDistributed();
|
||||
return new Cache\File();
|
||||
});
|
||||
|
||||
$this->registerService(Factory::class, function (Server $c) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use OCP\Files\NotPermittedException;
|
|||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\IAvatar;
|
||||
use OCP\IAvatarManager;
|
||||
use OCP\ICache;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUser;
|
||||
|
|
@ -54,6 +55,8 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
private $avatarFile;
|
||||
/** @var IAvatarManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $avatarManager;
|
||||
/** @var ICache|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cache;
|
||||
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $l;
|
||||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
|
|
@ -71,6 +74,8 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
parent::setUp();
|
||||
|
||||
$this->avatarManager = $this->getMockBuilder('OCP\IAvatarManager')->getMock();
|
||||
$this->cache = $this->getMockBuilder('OCP\ICache')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->l = $this->getMockBuilder(IL10N::class)->getMock();
|
||||
$this->l->method('t')->willReturnArgument(0);
|
||||
$this->userManager = $this->getMockBuilder(IUserManager::class)->getMock();
|
||||
|
|
@ -93,6 +98,7 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
'core',
|
||||
$this->request,
|
||||
$this->avatarManager,
|
||||
$this->cache,
|
||||
$this->l,
|
||||
$this->userManager,
|
||||
$this->rootFolder,
|
||||
|
|
@ -292,6 +298,25 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$this->assertEquals($expectedResponse, $this->avatarController->deleteAvatar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Trying to get a tmp avatar when it is not available. 404
|
||||
*/
|
||||
public function testTmpAvatarNoTmp(): void {
|
||||
$response = $this->avatarController->getTmpAvatar();
|
||||
$this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tmp avatar
|
||||
*/
|
||||
public function testTmpAvatarValid(): void {
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
|
||||
$response = $this->avatarController->getTmpAvatar();
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* When trying to post a new avatar a path or image should be posted.
|
||||
*/
|
||||
|
|
@ -310,6 +335,9 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$copyRes = copy(\OC::$SERVERROOT . '/tests/data/testimage.jpg', $fileName);
|
||||
$this->assertTrue($copyRes);
|
||||
|
||||
//Create file in cache
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
|
||||
//Create request return
|
||||
$reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => [filesize(\OC::$SERVERROOT . '/tests/data/testimage.jpg')]];
|
||||
$this->request->method('getUploadedFile')->willReturn($reqRet);
|
||||
|
|
@ -345,6 +373,9 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$copyRes = copy(\OC::$SERVERROOT . '/tests/data/testimage.gif', $fileName);
|
||||
$this->assertTrue($copyRes);
|
||||
|
||||
//Create file in cache
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.gif'));
|
||||
|
||||
//Create request return
|
||||
$reqRet = ['error' => [0], 'tmp_name' => [$fileName], 'size' => [filesize(\OC::$SERVERROOT . '/tests/data/testimage.gif')]];
|
||||
$this->request->method('getUploadedFile')->willReturn($reqRet);
|
||||
|
|
@ -433,6 +464,93 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$this->assertEquals($expectedResponse, $this->avatarController->postAvatar('avatar.jpg'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test what happens if the upload of the avatar fails
|
||||
*/
|
||||
public function testPostAvatarException(): void {
|
||||
$this->cache->expects($this->once())
|
||||
->method('set')
|
||||
->willThrowException(new \Exception('foo'));
|
||||
$file = $this->getMockBuilder('OCP\Files\File')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$file->expects($this->once())
|
||||
->method('getContent')
|
||||
->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
$file->expects($this->once())
|
||||
->method('getMimeType')
|
||||
->willReturn('image/jpeg');
|
||||
$userFolder = $this->getMockBuilder('OCP\Files\Folder')->getMock();
|
||||
$this->rootFolder->method('getUserFolder')->with('userid')->willReturn($userFolder);
|
||||
$userFolder->method('get')->willReturn($file);
|
||||
|
||||
$this->logger->expects($this->once())
|
||||
->method('error')
|
||||
->with('foo', ['exception' => new \Exception('foo'), 'app' => 'core']);
|
||||
$expectedResponse = new Http\JSONResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_OK);
|
||||
$this->assertEquals($expectedResponse, $this->avatarController->postAvatar('avatar.jpg'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test invalid crop argument
|
||||
*/
|
||||
public function testPostCroppedAvatarInvalidCrop(): void {
|
||||
$response = $this->avatarController->postCroppedAvatar([]);
|
||||
|
||||
$this->assertEquals(Http::STATUS_BAD_REQUEST, $response->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test no tmp avatar to crop
|
||||
*/
|
||||
public function testPostCroppedAvatarNoTmpAvatar(): void {
|
||||
$response = $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 10]);
|
||||
|
||||
$this->assertEquals(Http::STATUS_BAD_REQUEST, $response->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with non square crop
|
||||
*/
|
||||
public function testPostCroppedAvatarNoSquareCrop(): void {
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
|
||||
$this->avatarMock->method('set')->willThrowException(new \OC\NotSquareException);
|
||||
$this->avatarManager->method('getAvatar')->willReturn($this->avatarMock);
|
||||
$response = $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 11]);
|
||||
|
||||
$this->assertEquals(Http::STATUS_BAD_REQUEST, $response->getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for proper reply on proper crop argument
|
||||
*/
|
||||
public function testPostCroppedAvatarValidCrop(): void {
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
$this->avatarManager->method('getAvatar')->willReturn($this->avatarMock);
|
||||
$response = $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 10]);
|
||||
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
$this->assertEquals('success', $response->getData()['status']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test what happens if the cropping of the avatar fails
|
||||
*/
|
||||
public function testPostCroppedAvatarException(): void {
|
||||
$this->cache->method('get')->willReturn(file_get_contents(\OC::$SERVERROOT . '/tests/data/testimage.jpg'));
|
||||
|
||||
$this->avatarMock->method('set')->willThrowException(new \Exception('foo'));
|
||||
$this->avatarManager->method('getAvatar')->willReturn($this->avatarMock);
|
||||
|
||||
$this->logger->expects($this->once())
|
||||
->method('error')
|
||||
->with('foo', ['exception' => new \Exception('foo'), 'app' => 'core']);
|
||||
$expectedResponse = new Http\JSONResponse(['data' => ['message' => 'An error occurred. Please contact your admin.']], Http::STATUS_BAD_REQUEST);
|
||||
$this->assertEquals($expectedResponse, $this->avatarController->postCroppedAvatar(['x' => 0, 'y' => 0, 'w' => 10, 'h' => 11]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for proper reply on proper crop argument
|
||||
*/
|
||||
|
|
|
|||
65
tests/lib/Cache/CappedMemoryCacheTest.php
Normal file
65
tests/lib/Cache/CappedMemoryCacheTest.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Test\Cache;
|
||||
|
||||
use OCP\Cache\CappedMemoryCache;
|
||||
|
||||
/**
|
||||
* Class CappedMemoryCacheTest
|
||||
*
|
||||
* @package Test\Cache
|
||||
*/
|
||||
class CappedMemoryCacheTest extends TestCache {
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->instance = new CappedMemoryCache();
|
||||
}
|
||||
|
||||
public function testSetOverCap(): void {
|
||||
$instance = new CappedMemoryCache(3);
|
||||
|
||||
$instance->set('1', 'a');
|
||||
$instance->set('2', 'b');
|
||||
$instance->set('3', 'c');
|
||||
$instance->set('4', 'd');
|
||||
$instance->set('5', 'e');
|
||||
|
||||
$this->assertFalse($instance->hasKey('1'));
|
||||
$this->assertFalse($instance->hasKey('2'));
|
||||
$this->assertTrue($instance->hasKey('3'));
|
||||
$this->assertTrue($instance->hasKey('4'));
|
||||
$this->assertTrue($instance->hasKey('5'));
|
||||
}
|
||||
|
||||
public function testClear(): void {
|
||||
$value = 'ipsum lorum';
|
||||
$this->instance->set('1_value1', $value);
|
||||
$this->instance->set('1_value2', $value);
|
||||
$this->instance->set('2_value1', $value);
|
||||
$this->instance->set('3_value1', $value);
|
||||
|
||||
$this->assertTrue($this->instance->clear());
|
||||
$this->assertFalse($this->instance->hasKey('1_value1'));
|
||||
$this->assertFalse($this->instance->hasKey('1_value2'));
|
||||
$this->assertFalse($this->instance->hasKey('2_value1'));
|
||||
$this->assertFalse($this->instance->hasKey('3_value1'));
|
||||
}
|
||||
|
||||
public function testIndirectSet(): void {
|
||||
$this->instance->set('array', []);
|
||||
|
||||
$this->instance['array'][] = 'foo';
|
||||
|
||||
$this->assertEquals(['foo'], $this->instance->get('array'));
|
||||
|
||||
$this->instance['array']['bar'] = 'qwerty';
|
||||
|
||||
$this->assertEquals(['foo', 'bar' => 'qwerty'], $this->instance->get('array'));
|
||||
}
|
||||
}
|
||||
160
tests/lib/Cache/FileCacheTest.php
Normal file
160
tests/lib/Cache/FileCacheTest.php
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Test\Cache;
|
||||
|
||||
use OC\Cache\File;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Storage\Local;
|
||||
use OC\Files\Storage\Storage;
|
||||
use OC\Files\Storage\Temporary;
|
||||
use OC\Files\View;
|
||||
use OCP\Files\LockNotAcquiredException;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\ITempManager;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\Server;
|
||||
use Test\Traits\UserTrait;
|
||||
|
||||
/**
|
||||
* Class FileCacheTest
|
||||
*
|
||||
* @group DB
|
||||
*
|
||||
* @package Test\Cache
|
||||
*/
|
||||
class FileCacheTest extends TestCache {
|
||||
use UserTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* */
|
||||
private $user;
|
||||
/**
|
||||
* @var string
|
||||
* */
|
||||
private $datadir;
|
||||
/**
|
||||
* @var Storage
|
||||
* */
|
||||
private $storage;
|
||||
/**
|
||||
* @var View
|
||||
* */
|
||||
private $rootView;
|
||||
|
||||
public function skip() {
|
||||
//$this->skipUnless(OC_User::isLoggedIn());
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
//login
|
||||
$this->createUser('test', 'test');
|
||||
|
||||
$this->user = \OC_User::getUser();
|
||||
\OC_User::setUserId('test');
|
||||
|
||||
//clear all proxies and hooks so we can do clean testing
|
||||
\OC_Hook::clear('OC_Filesystem');
|
||||
|
||||
/** @var IMountManager $manager */
|
||||
$manager = Server::get(IMountManager::class);
|
||||
$manager->removeMount('/test');
|
||||
|
||||
$storage = new Temporary([]);
|
||||
Filesystem::mount($storage, [], '/test/cache');
|
||||
|
||||
//set up the users dir
|
||||
$this->rootView = new View('');
|
||||
$this->rootView->mkdir('/test');
|
||||
|
||||
$this->instance = new File();
|
||||
|
||||
// forces creation of cache folder for subsequent tests
|
||||
$this->instance->set('hack', 'hack');
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
if ($this->instance) {
|
||||
$this->instance->remove('hack', 'hack');
|
||||
}
|
||||
|
||||
\OC_User::setUserId($this->user);
|
||||
|
||||
if ($this->instance) {
|
||||
$this->instance->clear();
|
||||
$this->instance = null;
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
private function setupMockStorage() {
|
||||
$mockStorage = $this->getMockBuilder(Local::class)
|
||||
->onlyMethods(['filemtime', 'unlink'])
|
||||
->setConstructorArgs([['datadir' => Server::get(ITempManager::class)->getTemporaryFolder()]])
|
||||
->getMock();
|
||||
|
||||
Filesystem::mount($mockStorage, [], '/test/cache');
|
||||
|
||||
return $mockStorage;
|
||||
}
|
||||
|
||||
public function testGarbageCollectOldKeys(): void {
|
||||
$mockStorage = $this->setupMockStorage();
|
||||
|
||||
$mockStorage->expects($this->atLeastOnce())
|
||||
->method('filemtime')
|
||||
->willReturn(100);
|
||||
$mockStorage->expects($this->once())
|
||||
->method('unlink')
|
||||
->with('key1')
|
||||
->willReturn(true);
|
||||
|
||||
$this->instance->set('key1', 'value1');
|
||||
$this->instance->gc();
|
||||
}
|
||||
|
||||
public function testGarbageCollectLeaveRecentKeys(): void {
|
||||
$mockStorage = $this->setupMockStorage();
|
||||
|
||||
$mockStorage->expects($this->atLeastOnce())
|
||||
->method('filemtime')
|
||||
->willReturn(time() + 3600);
|
||||
$mockStorage->expects($this->never())
|
||||
->method('unlink')
|
||||
->with('key1');
|
||||
$this->instance->set('key1', 'value1');
|
||||
$this->instance->gc();
|
||||
}
|
||||
|
||||
public static function lockExceptionProvider(): array {
|
||||
return [
|
||||
[new LockedException('key1')],
|
||||
[new LockNotAcquiredException('key1', 1)],
|
||||
];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('lockExceptionProvider')]
|
||||
public function testGarbageCollectIgnoreLockedKeys($testException): void {
|
||||
$mockStorage = $this->setupMockStorage();
|
||||
|
||||
$mockStorage->expects($this->atLeastOnce())
|
||||
->method('filemtime')
|
||||
->willReturn(100);
|
||||
$mockStorage->expects($this->atLeastOnce())
|
||||
->method('unlink')->willReturnOnConsecutiveCalls($this->throwException($testException), $this->returnValue(true));
|
||||
|
||||
$this->instance->set('key1', 'value1');
|
||||
$this->instance->set('key2', 'value2');
|
||||
|
||||
$this->instance->gc();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue