aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/base.php1
-rw-r--r--lib/l10n/sw.js1
-rw-r--r--lib/l10n/sw.json1
-rw-r--r--lib/l10n/tr.js10
-rw-r--r--lib/l10n/tr.json10
-rw-r--r--lib/private/AppFramework/Http/Request.php22
-rw-r--r--lib/private/Files/Storage/Wrapper/Encryption.php20
-rw-r--r--lib/private/Log/Syslog.php8
-rw-r--r--lib/private/Profile/ProfileManager.php4
-rw-r--r--lib/private/TaskProcessing/Manager.php33
-rw-r--r--lib/public/IRequest.php10
-rw-r--r--lib/public/TaskProcessing/IManager.php4
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