diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/base.php | 1 | ||||
-rw-r--r-- | lib/l10n/sw.js | 1 | ||||
-rw-r--r-- | lib/l10n/sw.json | 1 | ||||
-rw-r--r-- | lib/l10n/tr.js | 10 | ||||
-rw-r--r-- | lib/l10n/tr.json | 10 | ||||
-rw-r--r-- | lib/private/AppFramework/Http/Request.php | 22 | ||||
-rw-r--r-- | lib/private/Files/Storage/Wrapper/Encryption.php | 20 | ||||
-rw-r--r-- | lib/private/Log/Syslog.php | 8 | ||||
-rw-r--r-- | lib/private/Profile/ProfileManager.php | 4 | ||||
-rw-r--r-- | lib/private/TaskProcessing/Manager.php | 33 | ||||
-rw-r--r-- | lib/public/IRequest.php | 10 | ||||
-rw-r--r-- | lib/public/TaskProcessing/IManager.php | 4 |
12 files changed, 101 insertions, 23 deletions
diff --git a/lib/base.php b/lib/base.php index 8613ab62ef1..7bfef10fec2 100644 --- a/lib/base.php +++ b/lib/base.php @@ -987,6 +987,7 @@ class OC { } $request = Server::get(IRequest::class); + $request->throwDecodingExceptionIfAny(); $requestPath = $request->getRawPathInfo(); if ($requestPath === '/heartbeat') { return; diff --git a/lib/l10n/sw.js b/lib/l10n/sw.js index 1e14a7e863a..51d62a7efd3 100644 --- a/lib/l10n/sw.js +++ b/lib/l10n/sw.js @@ -14,6 +14,7 @@ OC.L10N.register( "Filenames must not end with \"%1$s\"." : "Majina ya faili hayapaswi kuishia na \"%1$s\"", "File already exists" : "Faili lipo tayari", "Templates" : "Violezo", + "__language_name__" : "_lugha_jina_", "Apps" : "Maombi", "Settings" : "Mipangilio", "Log out" : "Ondoka", diff --git a/lib/l10n/sw.json b/lib/l10n/sw.json index a4d0bdc97d8..b3b31aeb4f7 100644 --- a/lib/l10n/sw.json +++ b/lib/l10n/sw.json @@ -12,6 +12,7 @@ "Filenames must not end with \"%1$s\"." : "Majina ya faili hayapaswi kuishia na \"%1$s\"", "File already exists" : "Faili lipo tayari", "Templates" : "Violezo", + "__language_name__" : "_lugha_jina_", "Apps" : "Maombi", "Settings" : "Mipangilio", "Log out" : "Ondoka", diff --git a/lib/l10n/tr.js b/lib/l10n/tr.js index 5f28ef658ab..6f9f8b4f128 100644 --- a/lib/l10n/tr.js +++ b/lib/l10n/tr.js @@ -337,11 +337,11 @@ OC.L10N.register( "A chat message to send to the agent." : "Aracıya gönderilecek sohbet iletisi.", "Confirmation" : "Onaylama", "Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "Daha önce istenilen işlemlerin onaylanıp onaylanmayacağı: Reddedilmesi için 0, onaylanması için 1.", - "Conversation token" : "Yazışma kodu", - "A token representing the conversation." : "Yazışmanın kodu.", + "Conversation token" : "Görüşme kodu", + "A token representing the conversation." : "Görüşmenin kodu.", "Generated response" : "Üretilen yanıt", "The response from the chat model." : "Sohbet modelinin yanıtı.", - "The new conversation token" : "Yeni sohbet kodu", + "The new conversation token" : "Yeni görüşme kodu", "Send this along with the next interaction." : "Bunu sonraki etkileşimin yanında gönderin.", "Requested actions by the agent" : "Aracı tarafından istenilen işlemler", "Actions that the agent would like to carry out in JSON format." : "Aracının JSON biçiminde gerçekleştirmesi istenen işlemler.", @@ -385,11 +385,11 @@ OC.L10N.register( "Chat" : "Sohbet", "Chat with the assistant" : "Yardımcı ile sohbet et", "System prompt" : "Sistem istemi", - "Define rules and assumptions that the assistant should follow during the conversation." : "Sohbet sırasında yardımcının uyması gereken kuralları ve varsayımları tanımlayın.", + "Define rules and assumptions that the assistant should follow during the conversation." : "Görüşme sırasında yardımcının uyması gereken kuralları ve varsayımları tanımlayın.", "Chat history" : "Sohbet geçmişi", "The history of chat messages before the current message, starting with a message by the user" : "Kullanıcının iletisiyle başlayarak, geçerli iletiden önceki sohbet iletilerinin geçmişi", "Response message" : "Yanıt iletisi", - "The generated response as part of the conversation" : "Sohbetin parçası olarak oluşturulan yanıt", + "The generated response as part of the conversation" : "Görüşmenin parçası olarak oluşturulan yanıt", "Chat with tools" : "Araçlar ile sohbet", "Chat with the language model with tool calling support." : "Araç çağırma desteği ile dil modeliyle sohbet edin.", "Tool message" : "Araç iletisi", diff --git a/lib/l10n/tr.json b/lib/l10n/tr.json index 2e46571c57f..2d5fd38f4c7 100644 --- a/lib/l10n/tr.json +++ b/lib/l10n/tr.json @@ -335,11 +335,11 @@ "A chat message to send to the agent." : "Aracıya gönderilecek sohbet iletisi.", "Confirmation" : "Onaylama", "Whether to confirm previously requested actions: 0 for denial and 1 for confirmation." : "Daha önce istenilen işlemlerin onaylanıp onaylanmayacağı: Reddedilmesi için 0, onaylanması için 1.", - "Conversation token" : "Yazışma kodu", - "A token representing the conversation." : "Yazışmanın kodu.", + "Conversation token" : "Görüşme kodu", + "A token representing the conversation." : "Görüşmenin kodu.", "Generated response" : "Üretilen yanıt", "The response from the chat model." : "Sohbet modelinin yanıtı.", - "The new conversation token" : "Yeni sohbet kodu", + "The new conversation token" : "Yeni görüşme kodu", "Send this along with the next interaction." : "Bunu sonraki etkileşimin yanında gönderin.", "Requested actions by the agent" : "Aracı tarafından istenilen işlemler", "Actions that the agent would like to carry out in JSON format." : "Aracının JSON biçiminde gerçekleştirmesi istenen işlemler.", @@ -383,11 +383,11 @@ "Chat" : "Sohbet", "Chat with the assistant" : "Yardımcı ile sohbet et", "System prompt" : "Sistem istemi", - "Define rules and assumptions that the assistant should follow during the conversation." : "Sohbet sırasında yardımcının uyması gereken kuralları ve varsayımları tanımlayın.", + "Define rules and assumptions that the assistant should follow during the conversation." : "Görüşme sırasında yardımcının uyması gereken kuralları ve varsayımları tanımlayın.", "Chat history" : "Sohbet geçmişi", "The history of chat messages before the current message, starting with a message by the user" : "Kullanıcının iletisiyle başlayarak, geçerli iletiden önceki sohbet iletilerinin geçmişi", "Response message" : "Yanıt iletisi", - "The generated response as part of the conversation" : "Sohbetin parçası olarak oluşturulan yanıt", + "The generated response as part of the conversation" : "Görüşmenin parçası olarak oluşturulan yanıt", "Chat with tools" : "Araçlar ile sohbet", "Chat with the language model with tool calling support." : "Araç çağırma desteği ile dil modeliyle sohbet edin.", "Tool message" : "Araç iletisi", diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index d177221556c..e662cb8679a 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -45,7 +45,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/'; protected string $inputStream; - protected $content; + private bool $isPutStreamContentAlreadySent = false; protected array $items = []; protected array $allowedKeys = [ 'get', @@ -64,6 +64,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected ?CsrfTokenManager $csrfTokenManager; protected bool $contentDecoded = false; + private ?\JsonException $decodingException = null; /** * @param array $vars An associative array with the following optional values: @@ -356,13 +357,13 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected function getContent() { // If the content can't be parsed into an array then return a stream resource. if ($this->isPutStreamContent()) { - if ($this->content === false) { + if ($this->isPutStreamContentAlreadySent) { throw new \LogicException( '"put" can only be accessed once if not ' . 'application/x-www-form-urlencoded or application/json.' ); } - $this->content = false; + $this->isPutStreamContentAlreadySent = true; return fopen($this->inputStream, 'rb'); } else { $this->decodeContent(); @@ -389,7 +390,14 @@ class Request implements \ArrayAccess, \Countable, IRequest { // 'application/json' and other JSON-related content types must be decoded manually. if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) { - $params = json_decode(file_get_contents($this->inputStream), true); + $content = file_get_contents($this->inputStream); + if ($content !== '') { + try { + $params = json_decode($content, true, flags:JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->decodingException = $e; + } + } if (\is_array($params) && \count($params) > 0) { $this->items['params'] = $params; if ($this->method === 'POST') { @@ -413,6 +421,12 @@ class Request implements \ArrayAccess, \Countable, IRequest { $this->contentDecoded = true; } + public function throwDecodingExceptionIfAny(): void { + if ($this->decodingException !== null) { + throw $this->decodingException; + } + } + /** * Checks if the CSRF check was correct diff --git a/lib/private/Files/Storage/Wrapper/Encryption.php b/lib/private/Files/Storage/Wrapper/Encryption.php index 51a5f99908d..58bd4dfddcf 100644 --- a/lib/private/Files/Storage/Wrapper/Encryption.php +++ b/lib/private/Files/Storage/Wrapper/Encryption.php @@ -23,6 +23,7 @@ use OCP\Encryption\IManager; use OCP\Encryption\Keys\IStorage; use OCP\Files; use OCP\Files\Cache\ICacheEntry; +use OCP\Files\GenericFileException; use OCP\Files\Mount\IMountPoint; use OCP\Files\Storage; use Psr\Log\LoggerInterface; @@ -685,12 +686,16 @@ class Encryption extends Wrapper { try { $source = $sourceStorage->fopen($sourceInternalPath, 'r'); $target = $this->fopen($targetInternalPath, 'w'); - [, $result] = Files::streamCopy($source, $target, true); + if ($source === false || $target === false) { + $result = false; + } else { + [, $result] = Files::streamCopy($source, $target, true); + } } finally { - if (is_resource($source)) { + if (isset($source) && $source !== false) { fclose($source); } - if (is_resource($target)) { + if (isset($target) && $target !== false) { fclose($target); } } @@ -740,6 +745,9 @@ class Encryption extends Wrapper { public function hash(string $type, string $path, bool $raw = false): string|false { $fh = $this->fopen($path, 'rb'); + if ($fh === false) { + return false; + } $ctx = hash_init($type); hash_update_stream($ctx, $fh); fclose($fh); @@ -764,6 +772,9 @@ class Encryption extends Wrapper { $firstBlock = ''; if ($this->storage->is_file($path)) { $handle = $this->storage->fopen($path, 'r'); + if ($handle === false) { + return ''; + } $firstBlock = fread($handle, $this->util->getHeaderSize()); fclose($handle); } @@ -894,6 +905,9 @@ class Encryption extends Wrapper { public function writeStream(string $path, $stream, ?int $size = null): int { // always fall back to fopen $target = $this->fopen($path, 'w'); + if ($target === false) { + throw new GenericFileException("Failed to open $path for writing"); + } [$count, $result] = Files::streamCopy($stream, $target, true); fclose($stream); fclose($target); diff --git a/lib/private/Log/Syslog.php b/lib/private/Log/Syslog.php index bd2c39509b1..46214599eb8 100644 --- a/lib/private/Log/Syslog.php +++ b/lib/private/Log/Syslog.php @@ -20,15 +20,18 @@ class Syslog extends LogDetails implements IWriter { ILogger::FATAL => LOG_CRIT, ]; + private string $tag; + public function __construct( SystemConfig $config, ?string $tag = null, ) { parent::__construct($config); if ($tag === null) { - $tag = $config->getValue('syslog_tag', 'Nextcloud'); + $this->tag = $config->getValue('syslog_tag', 'Nextcloud'); + } else { + $this->tag = $tag; } - openlog($tag, LOG_PID | LOG_CONS, LOG_USER); } public function __destruct() { @@ -41,6 +44,7 @@ class Syslog extends LogDetails implements IWriter { */ public function write(string $app, $message, int $level): void { $syslog_level = $this->levels[$level]; + openlog($this->tag, LOG_PID | LOG_CONS, LOG_USER); syslog($syslog_level, $this->logDetailsAsJSON($app, $message, $level)); } } diff --git a/lib/private/Profile/ProfileManager.php b/lib/private/Profile/ProfileManager.php index 84c7ea48373..1ade208fbcf 100644 --- a/lib/private/Profile/ProfileManager.php +++ b/lib/private/Profile/ProfileManager.php @@ -350,7 +350,7 @@ class ProfileManager implements IProfileManager { } /** - * Return the profile config of the target user with additional medatata, + * Return the profile config of the target user with additional metadata, * if a config does not already exist a default config is created and returned */ public function getProfileConfigWithMetadata(IUser $targetUser, ?IUser $visitingUser): array { @@ -399,7 +399,7 @@ class ProfileManager implements IProfileManager { ], IAccountManager::PROPERTY_ORGANISATION => [ 'appId' => self::CORE_APP_ID, - 'displayId' => $this->l10nFactory->get('lib')->t('Organisation'), + 'displayId' => $this->l10nFactory->get('lib')->t('Organization'), ], IAccountManager::PROPERTY_ROLE => [ 'appId' => self::CORE_APP_ID, diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index 9992310dbbb..0ffd141543f 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -36,6 +36,8 @@ use OCP\ICacheFactory; use OCP\IConfig; use OCP\IL10N; use OCP\IServerContainer; +use OCP\IUserManager; +use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Lock\LockedException; use OCP\SpeechToText\ISpeechToTextProvider; @@ -103,6 +105,8 @@ class Manager implements IManager { private IUserMountCache $userMountCache, private IClientService $clientService, private IAppManager $appManager, + private IUserManager $userManager, + private IUserSession $userSession, ICacheFactory $cacheFactory, ) { $this->appData = $appDataFactory->get('core'); @@ -809,7 +813,11 @@ class Manager implements IManager { throw new \OCP\TaskProcessing\Exception\Exception('No matching provider found'); } - public function getAvailableTaskTypes(bool $showDisabled = false): array { + public function getAvailableTaskTypes(bool $showDisabled = false, ?string $userId = null): array { + // userId will be obtained from the session if left to null + if (!$this->checkGuestAccess($userId)) { + return []; + } if ($this->availableTaskTypes === null) { $cachedValue = $this->distributedCache->get('available_task_types_v2'); if ($cachedValue !== null) { @@ -868,7 +876,27 @@ class Manager implements IManager { return isset($this->getAvailableTaskTypes()[$task->getTaskTypeId()]); } + private function checkGuestAccess(?string $userId = null): bool { + if ($userId === null && !$this->userSession->isLoggedIn()) { + return true; + } + if ($userId === null) { + $user = $this->userSession->getUser(); + } else { + $user = $this->userManager->get($userId); + } + + $guestsAllowed = $this->config->getAppValue('core', 'ai.taskprocessing_guests', 'false'); + if ($guestsAllowed == 'true' || !class_exists(\OCA\Guests\UserBackend::class) || !($user->getBackend() instanceof \OCA\Guests\UserBackend)) { + return true; + } + return false; + } + public function scheduleTask(Task $task): void { + if (!$this->checkGuestAccess($task->getUserId())) { + throw new \OCP\TaskProcessing\Exception\PreConditionNotMetException('Access to this resource is forbidden for guests.'); + } if (!$this->canHandleTask($task)) { throw new \OCP\TaskProcessing\Exception\PreConditionNotMetException('No task processing provider is installed that can handle this task type: ' . $task->getTaskTypeId()); } @@ -883,6 +911,9 @@ class Manager implements IManager { } public function runTask(Task $task): Task { + if (!$this->checkGuestAccess($task->getUserId())) { + throw new \OCP\TaskProcessing\Exception\PreConditionNotMetException('Access to this resource is forbidden for guests.'); + } if (!$this->canHandleTask($task)) { throw new \OCP\TaskProcessing\Exception\PreConditionNotMetException('No task processing provider is installed that can handle this task type: ' . $task->getTaskTypeId()); } diff --git a/lib/public/IRequest.php b/lib/public/IRequest.php index 2639a234ad5..cbac143d775 100644 --- a/lib/public/IRequest.php +++ b/lib/public/IRequest.php @@ -305,4 +305,14 @@ interface IRequest { * @since 8.1.0 */ public function getServerHost(): string; + + /** + * If decoding the request content failed, throw an exception. + * Currently only \JsonException for json decoding errors, + * but in the future may throw other exceptions for other decoding issues. + * + * @throws \Exception + * @since 32.0.0 + */ + public function throwDecodingExceptionIfAny(): void; } diff --git a/lib/public/TaskProcessing/IManager.php b/lib/public/TaskProcessing/IManager.php index 68825e82533..f161030f5f4 100644 --- a/lib/public/TaskProcessing/IManager.php +++ b/lib/public/TaskProcessing/IManager.php @@ -47,11 +47,13 @@ interface IManager { /** * @param bool $showDisabled if false, disabled task types will be filtered + * @param ?string $userId to check if the user is a guest. Will be obtained from session if left to default * @return array<string, array{name: string, description: string, inputShape: ShapeDescriptor[], inputShapeEnumValues: ShapeEnumValue[][], inputShapeDefaults: array<array-key, numeric|string>, optionalInputShape: ShapeDescriptor[], optionalInputShapeEnumValues: ShapeEnumValue[][], optionalInputShapeDefaults: array<array-key, numeric|string>, outputShape: ShapeDescriptor[], outputShapeEnumValues: ShapeEnumValue[][], optionalOutputShape: ShapeDescriptor[], optionalOutputShapeEnumValues: ShapeEnumValue[][]}> * @since 30.0.0 * @since 31.0.0 Added the `showDisabled` argument. + * @since 31.0.7 Added the `userId` argument */ - public function getAvailableTaskTypes(bool $showDisabled = false): array; + public function getAvailableTaskTypes(bool $showDisabled = false, ?string $userId = null): array; /** * @param Task $task The task to run |