-
Notifications
You must be signed in to change notification settings - Fork 120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(settings): add admin & user setting iframe #4373
Changes from all commits
6c519e1
0a1dccf
62c41a0
58f567b
73e2f70
f5320d2
e993dde
35f45ba
3a39075
c800205
c0e65e4
1660bca
dce7f65
786f1cb
388b2b9
5704fdc
fc0915a
bdfa8b9
6686315
dbf9d3a
82bb7a8
0e79c20
59a2192
747c528
5defde2
969a4f9
495a02c
9554d4f
7c7c779
bf69966
149e43f
e170b53
3fbe5e2
85483d6
fdb0200
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,11 +16,14 @@ | |
use OCA\Richdocuments\Exceptions\UnknownTokenException; | ||
use OCA\Richdocuments\Helper; | ||
use OCA\Richdocuments\PermissionManager; | ||
use OCA\Richdocuments\Service\CapabilitiesService; | ||
use OCA\Richdocuments\Service\FederationService; | ||
use OCA\Richdocuments\Service\SettingsService; | ||
use OCA\Richdocuments\Service\UserScopeService; | ||
use OCA\Richdocuments\TaskProcessingManager; | ||
use OCA\Richdocuments\TemplateManager; | ||
use OCA\Richdocuments\TokenManager; | ||
use OCA\Richdocuments\WOPI\SettingsUrl; | ||
use OCP\AppFramework\Controller; | ||
use OCP\AppFramework\Http; | ||
use OCP\AppFramework\Http\Attribute\FrontpageRoute; | ||
|
@@ -44,6 +47,7 @@ | |
use OCP\Files\Lock\OwnerLockedException; | ||
use OCP\Files\Node; | ||
use OCP\Files\NotFoundException; | ||
use OCP\Files\NotPermittedException; | ||
use OCP\IConfig; | ||
use OCP\IGroupManager; | ||
use OCP\IRequest; | ||
|
@@ -86,6 +90,8 @@ public function __construct( | |
private ILockManager $lockManager, | ||
private IEventDispatcher $eventDispatcher, | ||
private TaskProcessingManager $taskProcessingManager, | ||
private SettingsService $settingsService, | ||
private CapabilitiesService $capabilitiesService, | ||
) { | ||
parent::__construct($appName, $request); | ||
} | ||
|
@@ -133,11 +139,14 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons | |
} catch (NoLockProviderException|PreConditionNotMetException) { | ||
} | ||
|
||
$userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; | ||
|
||
|
||
$response = [ | ||
'BaseFileName' => $file->getName(), | ||
'Size' => $file->getSize(), | ||
'Version' => $version, | ||
'UserId' => !$isPublic ? $wopi->getEditorUid() : $guestUserId, | ||
'UserId' => $userId, | ||
'OwnerId' => $wopi->getOwnerUid(), | ||
'UserFriendlyName' => $userDisplayName, | ||
'UserExtraInfo' => [], | ||
|
@@ -167,6 +176,14 @@ public function checkFileInfo(string $fileId, string $access_token): JSONRespons | |
'ServerPrivateInfo' => [], | ||
]; | ||
|
||
if ($this->capabilitiesService->hasSettingIframeSupport()) { | ||
|
||
if (!$isPublic) { | ||
$response['UserSettings'] = $this->generateSettings($userId, 'userconfig'); | ||
} | ||
$response['SharedSettings'] = $this->generateSettings($userId, 'systemconfig'); | ||
} | ||
|
||
$enableZotero = $this->config->getAppValue(Application::APPNAME, 'zoteroEnabled', 'yes') === 'yes'; | ||
if (!$isPublic && $enableZotero) { | ||
$zoteroAPIKey = $this->config->getUserValue($wopi->getEditorUid(), 'richdocuments', 'zoteroAPIKey', ''); | ||
|
@@ -381,6 +398,111 @@ public function getFile(string $fileId, string $access_token): JSONResponse|Stre | |
} | ||
} | ||
|
||
#[NoAdminRequired] | ||
#[NoCSRFRequired] | ||
#[PublicPage] | ||
#[FrontpageRoute(verb: 'GET', url: 'wopi/settings')] | ||
public function getSettings(string $type, string $access_token): JSONResponse { | ||
if (empty($type)) { | ||
return new JSONResponse(['error' => 'Invalid type parameter'], Http::STATUS_BAD_REQUEST); | ||
} | ||
|
||
try { | ||
$wopi = $this->wopiMapper->getWopiForToken($access_token); | ||
if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) { | ||
return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_BAD_REQUEST); | ||
} | ||
|
||
$isPublic = empty($wopi->getEditorUid()); | ||
$guestUserId = 'Guest-' . \OC::$server->getSecureRandom()->generate(8); | ||
$userId = !$isPublic ? $wopi->getEditorUid() : $guestUserId; | ||
|
||
$userConfig = $this->settingsService->generateSettingsConfig($type, $userId); | ||
return new JSONResponse($userConfig, Http::STATUS_OK); | ||
} catch (UnknownTokenException|ExpiredTokenException $e) { | ||
$this->logger->debug($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => 'Unauthorized'], Http::STATUS_UNAUTHORIZED); | ||
} catch (\Exception $e) { | ||
$this->logger->error($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => 'Internal Server Error'], Http::STATUS_INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
#[NoAdminRequired] | ||
#[NoCSRFRequired] | ||
#[PublicPage] | ||
#[FrontpageRoute(verb: 'POST', url: 'wopi/settings/upload')] | ||
public function uploadSettingsFile(string $fileId, string $access_token): JSONResponse { | ||
try { | ||
$wopi = $this->wopiMapper->getWopiForToken($access_token); | ||
|
||
$userId = $wopi->getEditorUid(); | ||
|
||
$content = fopen('php://input', 'rb'); | ||
if (!$content) { | ||
throw new \Exception('Failed to read input stream.'); | ||
} | ||
|
||
$fileContent = stream_get_contents($content); | ||
fclose($content); | ||
|
||
// Use the fileId as a file path URL (e.g., "/settings/systemconfig/wordbook/en_US%20(1).dic") | ||
$settingsUrl = new SettingsUrl($fileId); | ||
$result = $this->settingsService->uploadFile($settingsUrl, $fileContent, $userId); | ||
|
||
return new JSONResponse([ | ||
'status' => 'success', | ||
'filename' => $settingsUrl->getFileName(), | ||
'details' => $result, | ||
], Http::STATUS_OK); | ||
|
||
} catch (UnknownTokenException $e) { | ||
$this->logger->debug($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => 'Invalid token'], Http::STATUS_FORBIDDEN); | ||
} catch (\Exception $e) { | ||
$this->logger->error($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
#[NoAdminRequired] | ||
#[NoCSRFRequired] | ||
#[PublicPage] | ||
#[FrontpageRoute(verb: 'DELETE', url: 'wopi/settings')] | ||
public function deleteSettingsFile(string $fileId, string $access_token): JSONResponse { | ||
try { | ||
$wopi = $this->wopiMapper->getWopiForToken($access_token); | ||
if ($wopi->getTokenType() !== Wopi::TOKEN_TYPE_SETTING_AUTH) { | ||
return new JSONResponse(['error' => 'Invalid token type'], Http::STATUS_FORBIDDEN); | ||
} | ||
|
||
// Parse the dynamic file path from `fileId`, e.g. "/settings/systemconfig/wordbook/en_US (1).dic" | ||
$settingsUrl = new SettingsUrl($fileId); | ||
$type = $settingsUrl->getType(); | ||
$category = $settingsUrl->getCategory(); | ||
$fileName = $settingsUrl->getFileName(); | ||
$userId = $wopi->getEditorUid(); | ||
|
||
$this->settingsService->deleteSettingsFile($type, $category, $fileName, $userId); | ||
|
||
return new JSONResponse([ | ||
'status' => 'success', | ||
'message' => "File '$fileName' deleted from '$category' of type '$type'." | ||
], Http::STATUS_OK); | ||
} catch (UnknownTokenException $e) { | ||
$this->logger->debug($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => 'Invalid token'], Http::STATUS_FORBIDDEN); | ||
} catch (NotFoundException $e) { | ||
return new JSONResponse(['error' => 'File not found'], Http::STATUS_NOT_FOUND); | ||
} catch (NotPermittedException $e) { | ||
return new JSONResponse(['error' => 'Not permitted'], Http::STATUS_FORBIDDEN); | ||
} catch (\Exception $e) { | ||
$this->logger->error($e->getMessage(), ['exception' => $e]); | ||
return new JSONResponse(['error' => 'Internal Server Error'], Http::STATUS_INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* Given an access token and a fileId, replaces the files with the request body. | ||
* Expects a valid token in access_token parameter. | ||
|
@@ -863,4 +985,17 @@ private function getWopiUrlForTemplate(Wopi $wopi): string { | |
$nextcloudUrl = $this->appConfig->getNextcloudUrl() ?: trim($this->urlGenerator->getAbsoluteURL(''), '/'); | ||
return $nextcloudUrl . '/index.php/apps/richdocuments/wopi/template/' . $wopi->getTemplateId() . '?access_token=' . $wopi->getToken(); | ||
} | ||
|
||
private function generateSettingToken(string $userId): string { | ||
return $this->settingsService->generateIframeToken('user', $userId)['token']; | ||
} | ||
|
||
private function generateSettings(string $userId, string $type): array { | ||
$nextcloudUrl = $this->appConfig->getNextcloudUrl() ?: trim($this->urlGenerator->getAbsoluteURL(''), '/'); | ||
$uri = $nextcloudUrl . '/index.php/apps/richdocuments/wopi/settings' . '?type=' . $type . '&access_token=' . $this->generateSettingToken($userId) . '&fileId=' . '-1'; | ||
return [ | ||
'uri' => $uri, | ||
'stamp' => time() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @juliusknorr As of now temporary I'm sending stamp as time but we need to pass here Category folders etag/stamp - May be we can create method inside settingsService but still we need fetch stamp from Folder/SimpleFolder or even Node. I try to test with Node etag it seems not updating is something updated in child. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As a simple approach you could try the using getMTime which is probably enough, but I'm not entirely sure this propagates as well If that doesn't we may need to manually update the etag whenever a file is added/updated/removed through richdocuments The following code should work for that then: $folder->getStorage()->getCache()->update($folder->getId(), [
'etag' => uniqid(),
]); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just for future reference, the reason the etag is not propagating directly is because we skip it in https://github.com/nextcloud/server/blob/41c53648ad18e3a94b6ba18ad0e6c7c09a520bd9/lib/private/Files/Storage/Common.php#L346 as in other cases we do not make use of that for anything in appdata or we have a separate mountpoint like for collectives where we can apply our own storage wrapper nextcloud/collectives@b54e6be but that won't work for the case here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do in next PR. |
||
]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker: While this is close to the WOPI protocol, we may want to separate it in its own controller at some point.