diff --git a/apps/encryption/lib/Crypto/Encryption.php b/apps/encryption/lib/Crypto/Encryption.php index daeb9859b41cc..e3f4f5a155279 100644 --- a/apps/encryption/lib/Crypto/Encryption.php +++ b/apps/encryption/lib/Crypto/Encryption.php @@ -1,5 +1,7 @@ useMasterPassword = $this->util->isMasterKeyEnabled(); } - /** - * @return string defining the technical unique id - */ - public function getId() { + public function getId(): string { return self::ID; } - /** - * In comparison to getKey() this function returns a human readable (maybe translated) name - * - * @return string - */ - public function getDisplayName() { + public function getDisplayName(): string { return self::DISPLAY_NAME; } - /** - * start receiving chunks from a file. This is the place where you can - * perform some initial step before starting encrypting/decrypting the - * chunks - * - * @param string $path to the file - * @param string $user who read/write the file - * @param string $mode php stream open mode - * @param array $header contains the header data read from the file - * @param array $accessList who has access to the file contains the key 'users' and 'public' - * - * @return array $header contain data as key-value pairs which should be - * written to the header, in case of a write operation - * or if no additional data is needed return a empty array - */ - public function begin($path, $user, $mode, array $header, array $accessList) { + public function begin(string $path, ?string $user, string $mode, array $header, array $accessList): array { $this->path = $this->getPathToRealFile($path); $this->accessList = $accessList; $this->user = $user; @@ -111,7 +80,6 @@ public function begin($path, $user, $mode, array $header, array $accessList) { $this->writeCache = ''; $this->useLegacyBase64Encoding = true; - if (isset($header['encoding'])) { $this->useLegacyBase64Encoding = $header['encoding'] !== Crypt::BINARY_ENCODING_FORMAT; } @@ -124,7 +92,7 @@ public function begin($path, $user, $mode, array $header, array $accessList) { } } - /* If useLegacyFileKey is not specified in header, auto-detect, to be safe */ + // If useLegacyFileKey is not specified in header, auto-detect, to be safe $useLegacyFileKey = (($header['useLegacyFileKey'] ?? '') == 'false' ? false : null); $this->fileKey = $this->keyManager->getFileKey($this->path, $useLegacyFileKey, $this->session->decryptAllModeActivated()); @@ -178,19 +146,10 @@ public function begin($path, $user, $mode, array $header, array $accessList) { } /** - * last chunk received. This is the place where you can perform some final - * operation and return some remaining data if something is left in your - * buffer. - * - * @param string $path to the file - * @param string $position - * @return string remained data which should be written to the file in case - * of a write operation * @throws PublicKeyMissingException - * @throws \Exception * @throws MultiKeyEncryptException */ - public function end($path, $position = '0') { + public function end(string $path, string $blockId = '0'): string { $result = ''; if ($this->isWriteOperation) { // in case of a part file we remember the new signature versions @@ -201,7 +160,7 @@ public function end($path, $position = '0') { self::$rememberVersion[$this->stripPartFileExtension($path)] = $this->version + 1; } if (!empty($this->writeCache)) { - $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $position); + $result = $this->crypt->symmetricEncryptFileContent($this->writeCache, $this->fileKey, $this->version + 1, $blockId); $this->writeCache = ''; } $publicKeys = []; @@ -239,22 +198,12 @@ public function end($path, $position = '0') { return $result ?: ''; } - - - /** - * encrypt data - * - * @param string $data you want to encrypt - * @param int $position - * @return string encrypted data - */ - public function encrypt($data, $position = 0) { + public function encrypt(string $data, string $blockId = '0'): string { // If extra data is left over from the last round, make sure it // is integrated into the next block if ($this->writeCache) { // Concat writeCache to start of $data $data = $this->writeCache . $data; - // Clear the write cache, ready for reuse - it has been // flushed and its old contents processed $this->writeCache = ''; @@ -286,7 +235,12 @@ public function encrypt($data, $position = 0) { // Read the chunk from the start of $data $chunk = substr($data, 0, $this->getUnencryptedBlockSize(true)); - $encrypted .= $this->crypt->symmetricEncryptFileContent($chunk, $this->fileKey, $this->version + 1, (string)$position); + $encrypted .= $this->crypt->symmetricEncryptFileContent( + $chunk, + $this->fileKey, + $this->version + 1, + $blockId + ); // Remove the chunk we just processed from // $data, leaving only unprocessed data in $data @@ -298,15 +252,7 @@ public function encrypt($data, $position = 0) { return $encrypted; } - /** - * decrypt data - * - * @param string $data you want to decrypt - * @param int|string $position - * @return string decrypted data - * @throws DecryptionFailedException - */ - public function decrypt($data, $position = 0) { + public function decrypt(string $data, string $blockId = '0'): string { if (empty($this->fileKey)) { $msg = 'Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.'; $hint = $this->l->t('Cannot decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you.'); @@ -315,18 +261,17 @@ public function decrypt($data, $position = 0) { throw new DecryptionFailedException($msg, $hint); } - return $this->crypt->symmetricDecryptFileContent($data, $this->fileKey, $this->cipher, $this->version, $position, !$this->useLegacyBase64Encoding); + return $this->crypt->symmetricDecryptFileContent( + $data, + $this->fileKey, + $this->cipher, + $this->version, + $blockId, + !$this->useLegacyBase64Encoding + ); } - /** - * update encrypted file, e.g. give additional users access to the file - * - * @param string $path path to the file which should be updated - * @param string $uid ignored - * @param array $accessList who has access to the file contains the key 'users' and 'public' - * @return bool - */ - public function update($path, $uid, array $accessList) { + public function update(string $path, ?string $uid, array $accessList): bool { if (empty($accessList)) { if (isset(self::$rememberVersion[$path])) { $this->keyManager->setVersion($path, self::$rememberVersion[$path], new View()); @@ -370,13 +315,7 @@ public function update($path, $uid, array $accessList) { return true; } - /** - * should the file be encrypted or not - * - * @param string $path - * @return boolean - */ - public function shouldEncrypt($path) { + public function shouldEncrypt(string $path): bool { if ($this->util->shouldEncryptHomeStorage() === false) { $storage = $this->util->getStorage($path); if ($storage && $storage->instanceOfStorage('\OCP\Files\IHomeStorage')) { @@ -402,25 +341,20 @@ public function shouldEncrypt($path) { } /** - * get size of the unencrypted payload per block. - * Nextcloud read/write files with a block size of 8192 byte + * Get size of the unencrypted payload per block. + * Nextcloud reads/writes files with a block size of 8192 byte. * - * Encrypted blocks have a 22-byte IV and 2 bytes of padding, encrypted and + * Encrypted blocks have a 22-byte IV and 2 bytes of padding; encrypted and * signed blocks have also a 71-byte signature and 1 more byte of padding, * resulting respectively in: - * - * 8192 - 22 - 2 = 8168 bytes in each unsigned unencrypted block - * 8192 - 22 - 2 - 71 - 1 = 8096 bytes in each signed unencrypted block + * 8192 - 22 - 2 = 8168 bytes (in each unsigned unencrypted block + * 8192 - 22 - 2 - 71 - 1 = 8096 bytes (in each signed unencrypted block) * * Legacy base64 encoding then reduces the available size by a 3/4 factor: - * - * 8168 * (3/4) = 6126 bytes in each base64-encoded unsigned unencrypted block - * 8096 * (3/4) = 6072 bytes in each base64-encoded signed unencrypted block - * - * @param bool $signed - * @return int + * 8168 * (3/4) = 6126 bytes (in each base64-encoded unsigned unencrypted block) + * 8096 * (3/4) = 6072 bytes (in each base64-encoded signed unencrypted block) */ - public function getUnencryptedBlockSize($signed = false) { + public function getUnencryptedBlockSize(bool $signed = false): int { if ($this->useLegacyBase64Encoding) { return $signed ? 6072 : 6126; } else { @@ -428,16 +362,7 @@ public function getUnencryptedBlockSize($signed = false) { } } - /** - * check if the encryption module is able to read the file, - * e.g. if all encryption keys exists - * - * @param string $path - * @param string $uid user for whom we want to check if they can read the file - * @return bool - * @throws DecryptionFailedException - */ - public function isReadable($path, $uid) { + public function isReadable(string $path, ?string $uid): bool { $fileKey = $this->keyManager->getFileKey($path, null); if (empty($fileKey)) { $owner = $this->util->getOwner($path); @@ -458,37 +383,36 @@ public function isReadable($path, $uid) { return true; } - /** - * Initial encryption of all files - * - * @param InputInterface $input - * @param OutputInterface $output write some status information to the terminal during encryption - */ - public function encryptAll(InputInterface $input, OutputInterface $output) { + public function encryptAll(InputInterface $input, OutputInterface $output): void { $this->encryptAll->encryptAll($input, $output); } - /** - * prepare module to perform decrypt all operation - * - * @param InputInterface $input - * @param OutputInterface $output - * @param string $user - * @return bool - */ - public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = '') { + public function prepareDecryptAll(InputInterface $input, OutputInterface $output, string $user = ''): bool { return $this->decryptAll->prepare($input, $output, $user); } + public function isReadyForUser(string $user): bool { + if ($this->util->isMasterKeyEnabled()) { + return true; + } + return $this->keyManager->userHasKeys($user); + } + + public function needDetailedAccessList(): bool { + return !$this->util->isMasterKeyEnabled(); + } /** - * @param string $path - * @return string + * Converts a versions file path to its canonical user file path. + * + * @param string $path File path (may be a versions path) + * @return string Canonical file path */ - protected function getPathToRealFile($path) { + protected function getPathToRealFile(string $path): string { $realPath = $path; $parts = explode('/', $path); if ($parts[2] === 'files_versions') { + // e.g., "/user/files_versions/document.txt.v1234567890" --> "/user/files/document.txt" $realPath = '/' . $parts[1] . '/files/' . implode('/', array_slice($parts, 3)); $length = strrpos($realPath, '.'); $realPath = substr($realPath, 0, $length); @@ -498,13 +422,13 @@ protected function getPathToRealFile($path) { } /** - * remove .part file extension and the ocTransferId from the file to get the - * original file name + * Removes the .part extension and ocTransferId from a part file path, + * returning the original file name. * - * @param string $path - * @return string + * @param string $path File path, possibly with .part extension and ocTransferId + * @return string Original file path without temporary upload markers */ - protected function stripPartFileExtension($path) { + protected function stripPartFileExtension(string $path): string { if (pathinfo($path, PATHINFO_EXTENSION) === 'part') { $pos = strrpos($path, '.', -6); $path = substr($path, 0, $pos); @@ -514,41 +438,15 @@ protected function stripPartFileExtension($path) { } /** - * get owner of a file + * Returns and caches the storage owner for a given file path. * - * @param string $path - * @return string + * @param string $path File path + * @return string User id of file owner */ - protected function getOwner($path) { + protected function getOwner(string $path): string { if (!isset($this->owner[$path])) { $this->owner[$path] = $this->util->getOwner($path); } return $this->owner[$path]; } - - /** - * Check if the module is ready to be used by that specific user. - * In case a module is not ready - because e.g. key pairs have not been generated - * upon login this method can return false before any operation starts and might - * cause issues during operations. - * - * @param string $user - * @return boolean - * @since 9.1.0 - */ - public function isReadyForUser($user) { - if ($this->util->isMasterKeyEnabled()) { - return true; - } - return $this->keyManager->userHasKeys($user); - } - - /** - * We only need a detailed access list if the master key is not enabled - * - * @return bool - */ - public function needDetailedAccessList() { - return !$this->util->isMasterKeyEnabled(); - } } diff --git a/apps/encryption/tests/Crypto/EncryptionTest.php b/apps/encryption/tests/Crypto/EncryptionTest.php index df9655cd2eb08..f7fcc49ec120e 100644 --- a/apps/encryption/tests/Crypto/EncryptionTest.php +++ b/apps/encryption/tests/Crypto/EncryptionTest.php @@ -95,8 +95,48 @@ public function testEndUser1(): void { ->method('decryptAllModeActivated') ->willReturn(false); + // Mocks for methods needed before begin() + $this->keyManagerMock->expects($this->any()) + ->method('getFileKey') + ->willReturn('fileKey'); + $this->cryptMock->expects($this->any()) + ->method('getCipher') + ->willReturn('AES-256-CTR'); + $this->cryptMock->expects($this->any()) + ->method('getLegacyCipher') + ->willReturn('AES-128-CFB'); + $this->cryptMock->expects($this->any()) + ->method('useLegacyBase64Encoding') + ->willReturn(false); + $this->cryptMock->expects($this->any()) + ->method('generateFileKey') + ->willReturn('fileKey'); + + // Prepare the post-begin mocks for end() phase + $this->keyManagerMock->expects($this->any()) + ->method('getPublicKey') + ->willReturnCallback([$this, 'getPublicKeyCallback']); + $this->keyManagerMock->expects($this->any()) + ->method('addSystemKeys') + ->willReturnCallback([$this, 'addSystemKeysCallback']); + $this->cryptMock->expects($this->any()) + ->method('multiKeyEncrypt') + ->willReturn([ + 'user1' => 'encForUser1', + 'user3' => 'encForUser3', + ]); + + $this->utilMock->expects($this->any()) + ->method('getOwner') + ->willReturn('user1'); + + // Begin the encryption process as user1, with user2 missing their public key $this->instance->begin('/foo/bar', 'user1', 'r', [], ['users' => ['user1', 'user2', 'user3']]); - $this->endTest(); + + // Set internal state to simulate write and call end() + self::invokePrivate($this->instance, 'isWriteOperation', [true]); + self::invokePrivate($this->instance, 'writeCache', ['']); + $this->instance->end('/foo/bar'); } /** @@ -108,36 +148,43 @@ public function testEndUser2(): void { ->method('decryptAllModeActivated') ->willReturn(false); - $this->expectException(PublicKeyMissingException::class); - - $this->instance->begin('/foo/bar', 'user2', 'r', [], ['users' => ['user1', 'user2', 'user3']]); - $this->endTest(); - } - - /** - * common part of testEndUser1 and testEndUser2 - * - * @throws PublicKeyMissingException - */ - public function endTest() { - // prepare internal variables - self::invokePrivate($this->instance, 'isWriteOperation', [true]); - self::invokePrivate($this->instance, 'writeCache', ['']); + // Mocks for methods needed before begin() + $this->keyManagerMock->expects($this->any()) + ->method('getFileKey') + ->willReturn('fileKey'); + $this->cryptMock->expects($this->any()) + ->method('getCipher') + ->willReturn('AES-256-CTR'); + $this->cryptMock->expects($this->any()) + ->method('getLegacyCipher') + ->willReturn('AES-128-CFB'); + $this->cryptMock->expects($this->any()) + ->method('useLegacyBase64Encoding') + ->willReturn(false); + $this->cryptMock->expects($this->any()) + ->method('generateFileKey') + ->willReturn('fileKey'); + // Prepare the post-begin mocks for end() phase $this->keyManagerMock->expects($this->any()) ->method('getPublicKey') ->willReturnCallback([$this, 'getPublicKeyCallback']); $this->keyManagerMock->expects($this->any()) ->method('addSystemKeys') ->willReturnCallback([$this, 'addSystemKeysCallback']); - $this->cryptMock->expects($this->any()) - ->method('multiKeyEncrypt') - ->willReturn([]); + $this->cryptMock->expects($this->never()) + ->method('multiKeyEncrypt'); + + $this->expectException(PublicKeyMissingException::class); + + $this->instance->begin('/foo/bar', 'user2', 'r', [], ['users' => ['user1', 'user2', 'user3']]); + // Set internal state to simulate write and call end() + self::invokePrivate($this->instance, 'isWriteOperation', [true]); + self::invokePrivate($this->instance, 'writeCache', ['']); $this->instance->end('/foo/bar'); } - public function getPublicKeyCallback($uid) { if ($uid === 'user2') { throw new PublicKeyMissingException($uid); @@ -235,6 +282,13 @@ public function testBeginDecryptAll(): void { ->with($path, null, true) ->willReturn($fileKey); + $this->cryptMock->expects($this->any()) + ->method('getCipher') + ->willReturn('AES-256-CTR'); + $this->cryptMock->expects($this->any()) + ->method('getLegacyCipher') + ->willReturn('AES-128-CFB'); + $this->instance->begin($path, 'user', 'r', [], []); $this->assertSame($fileKey, @@ -252,10 +306,18 @@ public function testBeginInitMasterKey(): void { ->method('decryptAllModeActivated') ->willReturn(false); - $this->sessionMock->expects($this->once())->method('isReady')->willReturn(false); - $this->utilMock->expects($this->once())->method('isMasterKeyEnabled') + $this->sessionMock->expects($this->once()) + ->method('isReady') + ->willReturn(false); + $this->utilMock->expects($this->once()) + ->method('isMasterKeyEnabled') ->willReturn(true); - $this->keyManagerMock->expects($this->once())->method('init')->with('', ''); + $this->keyManagerMock->expects($this->once()) + ->method('init') + ->with('', ''); + $this->cryptMock->expects($this->any()) + ->method('getLegacyCipher') + ->willReturn('anyWillDo'); $this->instance->begin('/user/files/welcome.txt', 'user', 'r', [], []); } @@ -282,6 +344,10 @@ public function testUpdate($fileKey, $expected): void { $this->keyManagerMock->expects($this->never())->method('getVersion'); $this->keyManagerMock->expects($this->never())->method('setVersion'); + $this->utilMock->expects($this->any()) + ->method('getOwner') + ->willReturn('user1'); + $this->assertSame($expected, $this->instance->update('path', 'user1', ['users' => ['user1']]) ); @@ -342,6 +408,10 @@ function ($fileKey, $publicKeys) { $this->keyManagerMock->expects($this->never())->method('getVersion'); $this->keyManagerMock->expects($this->never())->method('setVersion'); + $this->utilMock->expects($this->any()) + ->method('getOwner') + ->willReturn('user1'); + $this->assertTrue( $this->instance->update('path', 'user1', ['users' => ['user1']]) ); diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index 155ccd6ba42f4..0e182e41bb087 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -1083,9 +1083,6 @@ - - - diff --git a/lib/private/Files/Stream/Encryption.php b/lib/private/Files/Stream/Encryption.php index ef147ec421fb1..83c0da59cd8ca 100644 --- a/lib/private/Files/Stream/Encryption.php +++ b/lib/private/Files/Stream/Encryption.php @@ -392,8 +392,9 @@ public function stream_seek($offset, $whence = SEEK_SET) { public function stream_close() { $this->flush('end'); - $position = (int)floor($this->position / $this->unencryptedBlockSize); - $remainingData = $this->encryptionModule->end($this->fullPath, $position . 'end'); + $blockIndex = (int)floor($this->position / $this->unencryptedBlockSize); + $blockId = (string)$blockIndex . 'end'; + $remainingData = $this->encryptionModule->end($this->fullPath, $blockId); if ($this->readOnly === false) { if (!empty($remainingData)) { parent::stream_write($remainingData); @@ -407,7 +408,10 @@ public function stream_close() { $cacheEntry = $cache->get($this->internalPath); if ($cacheEntry) { $version = $cacheEntry['encryptedVersion'] + 1; - $cache->update($cacheEntry->getId(), ['encrypted' => $version, 'encryptedVersion' => $version, 'unencrypted_size' => $this->unencryptedSize]); + $cache->update( + $cacheEntry->getId(), + ['encrypted' => $version, 'encryptedVersion' => $version, 'unencrypted_size' => $this->unencryptedSize] + ); } } @@ -425,8 +429,9 @@ protected function flush($positionPrefix = '') { // automatically attempted when the file is written to disk - // we are handling that separately here and we don't want to // get into an infinite loop - $position = (int)floor($this->position / $this->unencryptedBlockSize); - $encrypted = $this->encryptionModule->encrypt($this->cache, $position . $positionPrefix); + $blockIndex = (int)floor($this->position / $this->unencryptedBlockSize); + $blockId = (string)$blockIndex . $positionPrefix; + $encrypted = $this->encryptionModule->encrypt($this->cache, $blockId); $bytesWritten = parent::stream_write($encrypted); $this->writeFlag = false; // Check whether the write concerns the last block @@ -453,12 +458,14 @@ protected function readCache() { if ($this->cache === '' && !($this->position === $this->unencryptedSize && ($this->position % $this->unencryptedBlockSize) === 0)) { // Get the data from the file handle $data = $this->stream_read_block($this->util->getBlockSize()); - $position = (int)floor($this->position / $this->unencryptedBlockSize); + $blockIndex = (int)floor($this->position / $this->unencryptedBlockSize); $numberOfChunks = (int)($this->unencryptedSize / $this->unencryptedBlockSize); - if ($numberOfChunks === $position) { - $position .= 'end'; + if ($numberOfChunks === $blockIndex) { + $blockId = (string)$blockIndex . 'end'; + } else { + $blockId = (string)$blockIndex; } - $this->cache = $this->encryptionModule->decrypt($data, $position); + $this->cache = $this->encryptionModule->decrypt($data, $blockId); } } diff --git a/lib/public/Encryption/IEncryptionModule.php b/lib/public/Encryption/IEncryptionModule.php index 0bf0ef3476beb..2692fa16a9567 100644 --- a/lib/public/Encryption/IEncryptionModule.php +++ b/lib/public/Encryption/IEncryptionModule.php @@ -1,5 +1,7 @@ end") + * @param string $blockId Block identifier of the last block (looks like "end") * * @return string remained data which should be written to the file in case * of a write operation * + * @throws \Exception + * * @since 8.1.0 * @since 9.0.0 parameter $position added */ - public function end($path, $position); + public function end(string $path, string $blockId = '0'): string; /** * encrypt data * * @param string $data you want to encrypt - * @param string $position position of the block we want to encrypt (starts with '0') + * @param string $blockId Block identifier representing the block index we want to encrypt + * (starts with '0'). Usually a numeric string (e.g. "0", "5"), but + * may have a special marker, such as "5end" to denote the final block. * - * @return mixed encrypted data + * @return string encrypted data * * @since 8.1.0 * @since 9.0.0 parameter $position added */ - public function encrypt($data, $position); + public function encrypt(string $data, string $blockId = '0'): string; /** * decrypt data * * @param string $data you want to decrypt - * @param int|string $position position of the block we want to decrypt + * @param string $blockId Block identifier representing the block index we want to decrypt + * + * @return string decrypted data * - * @return mixed decrypted data + * @throws DecryptionFailedException * * @since 8.1.0 * @since 9.0.0 parameter $position added */ - public function decrypt($data, $position); + public function decrypt(string $data, string $blockId = '0'): string; /** * update encrypted file, e.g. give additional users access to the file * * @param string $path path to the file which should be updated - * @param string $uid of the user who performs the operation + * @param null|string $uid of the user who performs the operation (null for public access). * @param array $accessList who has access to the file contains the key 'users' and 'public' - * @return boolean + * @return bool * @since 8.1.0 */ - public function update($path, $uid, array $accessList); + public function update(string $path, ?string $uid, array $accessList): bool; /** * should the file be encrypted or not * * @param string $path - * @return boolean + * @return bool * @since 8.1.0 */ - public function shouldEncrypt($path); + public function shouldEncrypt(string $path): bool; /** * get size of the unencrypted payload per block. @@ -118,18 +131,18 @@ public function shouldEncrypt($path); * @return int * @since 8.1.0 optional parameter $signed was added in 9.0.0 */ - public function getUnencryptedBlockSize($signed = false); + public function getUnencryptedBlockSize(bool $signed = false): int; /** * check if the encryption module is able to read the file, * e.g. if all encryption keys exists * * @param string $path - * @param string $uid user for whom we want to check if they can read the file - * @return boolean + * @param null|string $uid user for whom we want to check if they can read the file + * @return bool * @since 8.1.0 */ - public function isReadable($path, $uid); + public function isReadable(string $path, ?string $uid): bool; /** * Initial encryption of all files @@ -138,18 +151,18 @@ public function isReadable($path, $uid); * @param OutputInterface $output write some status information to the terminal during encryption * @since 8.2.0 */ - public function encryptAll(InputInterface $input, OutputInterface $output); + public function encryptAll(InputInterface $input, OutputInterface $output): void; /** * prepare encryption module to decrypt all files * * @param InputInterface $input * @param OutputInterface $output write some status information to the terminal during encryption - * @param $user (optional) for which the files should be decrypted, default = all users + * @param string $user (optional) for which the files should be decrypted, default = all users * @return bool return false on failure or if it isn't supported by the module * @since 8.2.0 */ - public function prepareDecryptAll(InputInterface $input, OutputInterface $output, $user = ''); + public function prepareDecryptAll(InputInterface $input, OutputInterface $output, string $user = ''): bool; /** * Check if the module is ready to be used by that specific user. @@ -158,10 +171,10 @@ public function prepareDecryptAll(InputInterface $input, OutputInterface $output * cause issues during operations. * * @param string $user - * @return boolean + * @return bool * @since 9.1.0 */ - public function isReadyForUser($user); + public function isReadyForUser(string $user): bool; /** * Does the encryption module needs a detailed list of users with access to the file? @@ -171,5 +184,5 @@ public function isReadyForUser($user); * @since 13.0.0 * @return bool */ - public function needDetailedAccessList(); + public function needDetailedAccessList(): bool; } diff --git a/tests/lib/Encryption/UpdateTest.php b/tests/lib/Encryption/UpdateTest.php index 04ca224c0a132..d00586e4f4c16 100644 --- a/tests/lib/Encryption/UpdateTest.php +++ b/tests/lib/Encryption/UpdateTest.php @@ -92,6 +92,7 @@ public function testUpdate($path, $isDir, $allFiles, $numberOfFiles): void { $this->encryptionManager->expects($this->once()) ->method('getEncryptionModule') ->willReturn($this->encryptionModule); + $this->encryptionModule->method('needDetailedAccessList')->willReturn(true); if ($isDir) { $this->util->expects($this->once())