Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added

- ExApps can register reference providers with `POST /ocs/v2.php/apps/app_api/api/v1/ui/reference-provider`. A provider adds an entry to the Smart Picker, optionally with a picker script from the ExApp, and resolves the links that match its patterns into rich previews through `POST /reference/resolve` on the ExApp. The script path and a relative icon path must stay inside the ExApp: no `..` segments, schemes, backslashes or percent-encoding. #1099

### Changed

- `POST /ocs/v2.php/apps/app_api/api/v1/notification` now answers `404` when the `notifications` app is not enabled, instead of `200` for a notification that nobody delivers.
Expand Down
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Two options are available:

See the [admin documentation](https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/DeployConfigurations.html) for setup instructions.
]]></description>
<version>36.0.0-dev.0</version>
<version>36.0.0-dev.1</version>
<licence>agpl</licence>
<author mail="[email protected]" homepage="https://github.com/andrey18106">Andrey Borysenko</author>
<author mail="[email protected]" homepage="https://github.com/bigcat88">Alexander Piskun</author>
Expand Down
5 changes: 5 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@
['name' => 'OCSUi#unregisterExAppMenuEntry', 'url' => '/api/v1/ui/top-menu', 'verb' => 'DELETE'],
['name' => 'OCSUi#getExAppMenuEntry', 'url' => '/api/v1/ui/top-menu', 'verb' => 'GET'],

// Reference providers (Smart Picker entries and link previews)
['name' => 'OCSUi#registerExAppReferenceProvider', 'url' => '/api/v1/ui/reference-provider', 'verb' => 'POST'],
['name' => 'OCSUi#unregisterExAppReferenceProvider', 'url' => '/api/v1/ui/reference-provider', 'verb' => 'DELETE'],
['name' => 'OCSUi#getExAppReferenceProvider', 'url' => '/api/v1/ui/reference-provider', 'verb' => 'GET'],

// Common UI
['name' => 'OCSUi#setExAppInitialState', 'url' => '/api/v1/ui/initial-state', 'verb' => 'POST'],
['name' => 'OCSUi#deleteExAppInitialState', 'url' => '/api/v1/ui/initial-state', 'verb' => 'DELETE'],
Expand Down
2 changes: 2 additions & 0 deletions js/app_api-reference.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions js/app_api-reference.js.license
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
SPDX-License-Identifier: ( AGPL-3.0-or-later)
SPDX-FileCopyrightText: Alexander Piskun <[email protected]>


This file is generated from multiple sources. Included packages:
- app_api
- version: 36.0.0-dev.0
- license: ( AGPL-3.0-or-later)
1 change: 1 addition & 0 deletions js/app_api-reference.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions js/app_api-reference.js.map.license
21 changes: 21 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
use OCA\AppAPI\Listener\GetTaskProcessingProvidersListener;
use OCA\AppAPI\Listener\LoadFilesPluginListener;
use OCA\AppAPI\Listener\LoadMenuEntriesListener;
use OCA\AppAPI\Listener\RenderReferenceListener;
use OCA\AppAPI\Listener\SabrePluginAuthInitListener;
use OCA\AppAPI\Middleware\AppAPIAuthMiddleware;
use OCA\AppAPI\Middleware\ExAppUIL10NMiddleware;
use OCA\AppAPI\Middleware\ExAppUiMiddleware;
use OCA\AppAPI\Middleware\MaintenanceModeMiddleware;
use OCA\AppAPI\Notifications\ExAppNotifier;
use OCA\AppAPI\PublicCapabilities;
use OCA\AppAPI\Service\UI\ReferenceProviderService;
use OCA\AppAPI\SetupChecks\DaemonCheck;
use OCA\AppAPI\SetupChecks\ExAppsErrorSetupCheck;
use OCA\AppAPI\SetupChecks\ExAppsWarningSetupCheck;
Expand All @@ -35,12 +37,15 @@
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Navigation\Events\LoadAdditionalEntriesEvent;
use OCP\Settings\Events\DeclarativeSettingsGetValueEvent;
use OCP\Settings\Events\DeclarativeSettingsRegisterFormEvent;
use OCP\Settings\Events\DeclarativeSettingsSetValueEvent;
use OCP\TaskProcessing\Events\GetTaskProcessingProvidersEvent;
use Psr\Container\ContainerExceptionInterface;
use Psr\Log\LoggerInterface;

class Application extends App implements IBootstrap {
public const APP_ID = 'app_api';
Expand Down Expand Up @@ -78,11 +83,27 @@ public function register(IRegistrationContext $context): void {
$context->registerSetupCheck(HarpVersionCheck::class);
$context->registerSetupCheck(ExAppsErrorSetupCheck::class);
$context->registerSetupCheck(ExAppsWarningSetupCheck::class);

$context->registerEventListener(RenderReferenceEvent::class, RenderReferenceListener::class);
$this->registerExAppReferenceProviders($context);
}

public function boot(IBootContext $context): void {
}

/**
* The server has no event to collect reference providers, so they are registered here,
* one synthetic class per ExApp provider (read from the distributed cache when available).
*/
private function registerExAppReferenceProviders(IRegistrationContext $context): void {
$container = $this->getContainer();
try {
$container->get(ReferenceProviderService::class)->registerExAppReferenceProviders($context);
} catch (ContainerExceptionInterface $e) {
$container->get(LoggerInterface::class)->debug('Could not register ExApp reference providers', ['exception' => $e]);
}
}

public function registerDavAuth(): void {
$container = $this->getContainer();

Expand Down
86 changes: 86 additions & 0 deletions lib/Controller/OCSUiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCA\AppAPI\ResponseDefinitions;
use OCA\AppAPI\Service\UI\FilesActionsMenuService;
use OCA\AppAPI\Service\UI\InitialStateService;
use OCA\AppAPI\Service\UI\ReferenceProviderService;
use OCA\AppAPI\Service\UI\ScriptsService;
use OCA\AppAPI\Service\UI\StylesService;
use OCA\AppAPI\Service\UI\TopMenuService;
Expand All @@ -32,6 +33,7 @@
* @psalm-import-type AppAPIInitialState from ResponseDefinitions
* @psalm-import-type AppAPIScript from ResponseDefinitions
* @psalm-import-type AppAPIStyle from ResponseDefinitions
* @psalm-import-type AppAPIReferenceProvider from ResponseDefinitions
*/
class OCSUiController extends OCSController {
protected $request;
Expand All @@ -43,6 +45,7 @@ public function __construct(
private readonly InitialStateService $initialStateService,
private readonly ScriptsService $scriptsService,
private readonly StylesService $stylesService,
private readonly ReferenceProviderService $referenceProviderService,
) {
parent::__construct(Application::APP_ID, $request);

Expand Down Expand Up @@ -230,6 +233,89 @@ public function getExAppMenuEntry(string $name): DataResponse {
return new DataResponse($result->jsonSerialize(), Http::STATUS_OK);
}

/**
* Register a reference provider for the calling ExApp
*
* The provider appears in the Smart Picker and resolves matching links into rich previews.
* A link that matches one of `matchPatterns` is resolved with `POST /reference/resolve` on the ExApp.
* Every link a user pastes or renders that matches the patterns is sent to the ExApp, with the user's ID,
* so the patterns must name the ExApp's own hosts.
*
* @param string $providerId Unique ID of the provider; the picker script registers its custom element under this ID
* @param string $title Title in the Smart Picker
* @param string $iconUrl Icon path inside the ExApp, served through HaRP or the AppAPI proxy
* @param int $order Position in the Smart Picker; lower comes first
* @param list<string> $searchProvidersIds Unified search providers that the picker can use for this provider, at most 10
* @param list<string> $matchPatterns PCRE patterns of links that this provider resolves, at most 10 of up to 256 characters. Each must start with `^https://` (or `^https?://`) and a literal host, like `#^https://example\.com/item/\d+$#`. A pattern only matches links on that scheme and host
* @param string $scriptPath Path of the picker script inside the ExApp; empty for no custom picker element
*
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws OCSBadRequestException Invalid input, or the provider could not be registered
*
* 200: Reference provider registered
*/
#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function registerExAppReferenceProvider(
string $providerId, string $title, string $iconUrl = '', int $order = 0,
array $searchProvidersIds = [], array $matchPatterns = [], string $scriptPath = ''): DataResponse {
try {
$result = $this->referenceProviderService->registerReferenceProvider(
$this->request->getHeader('ex-app-id'), $providerId, $title, $iconUrl, $order,
$searchProvidersIds, $matchPatterns, $scriptPath);
} catch (\InvalidArgumentException $e) {
throw new OCSBadRequestException($e->getMessage());
}
if (!$result) {
throw new OCSBadRequestException('Reference provider could not be registered');
}
return new DataResponse();
}

/**
* Unregister a reference provider of the calling ExApp
*
* @param string $providerId ID of the provider to remove
*
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws OCSNotFoundException Reference provider not found
*
* 200: Reference provider unregistered
*/
#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function unregisterExAppReferenceProvider(string $providerId): DataResponse {
if (!$this->referenceProviderService->unregisterReferenceProvider(
$this->request->getHeader('ex-app-id'), $providerId)) {
throw new OCSNotFoundException('No such reference provider');
}
return new DataResponse();
}

/**
* Get a reference provider of the calling ExApp
*
* @param string $providerId ID of the provider
*
* @return DataResponse<Http::STATUS_OK, AppAPIReferenceProvider, array{}>
* @throws OCSNotFoundException Reference provider not found
*
* 200: Reference provider returned
*/
#[AppAPIAuth]
#[PublicPage]
#[NoCSRFRequired]
public function getExAppReferenceProvider(string $providerId): DataResponse {
$result = $this->referenceProviderService->getReferenceProvider(
$this->request->getHeader('ex-app-id'), $providerId);
if (!$result) {
throw new OCSNotFoundException('No such reference provider');
}
return new DataResponse($result->jsonSerialize(), Http::STATUS_OK);
}

/**
* Set an initial state for a page of the calling ExApp
*
Expand Down
Loading