diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18dd35bc..d12b23d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
-## [2.4.0 - 2024-04-0x]
+## [2.4.0 - 2024-04-04]
### Added
- API for listening to file system events. #259
+### Changed
+
+- Optimizations(1) related to speed up handling the incoming ExApps requests. #262
+- `occ app_api:scopes:list` command removed as not needed. #262
+
### Fixed
- Corrected error handling for `occ` commands: `register` and `update`. #258
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 38e78a31..6fd8ea55 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -91,7 +91,6 @@ to join us in shaping a more versatile, stable, and secure app landscape.
OCA\AppAPI\Command\Daemon\RegisterDaemon
OCA\AppAPI\Command\Daemon\UnregisterDaemon
OCA\AppAPI\Command\Daemon\ListDaemons
- OCA\AppAPI\Command\ApiScopes\ListApiScopes
OCA\AppAPI\Settings\Admin
diff --git a/docs/ManagingExternalApplications.rst b/docs/ManagingExternalApplications.rst
index 6e144cee..2eb34bae 100644
--- a/docs/ManagingExternalApplications.rst
+++ b/docs/ManagingExternalApplications.rst
@@ -116,13 +116,6 @@ System user
System user (``[system user]``) in the list means that this ExApp was setup as a system ExApp.
-List ExApp Scopes
------------------
-
-List accepted scopes (see :ref:`api_scopes`) for ExApp.
-
-Command: ``app_api:app:scopes:list ``
-
Using the ExApp Management UI
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/tech_details/ApiScopes.rst b/docs/tech_details/ApiScopes.rst
index 5952ffb2..1e70ec2f 100644
--- a/docs/tech_details/ApiScopes.rst
+++ b/docs/tech_details/ApiScopes.rst
@@ -40,9 +40,6 @@ tailored access to the functionalities they need, enhancing performance and user
As Nextcloud evolves, this list of API groups will continue to grow, offering developers a wide array of tools
to create innovative and efficient applications.
-The command to list registered scopes, `occ app_api:scopes:list`, remains an invaluable tool for developers
-and administrators, offering a quick and easy way to verify the API scopes available and required by applications within the Nextcloud platform.
-
The streamlined approach to API scopes not only simplifies the application development process
but also aligns with best practices in software design, emphasizing clarity, security, and efficiency.
This refinement in the handling of API scopes reflects Nextcloud's commitment to providing a robust and developer-friendly platform.
diff --git a/docs/tech_details/Authentication.rst b/docs/tech_details/Authentication.rst
index b7d4c7cf..8e99daba 100644
--- a/docs/tech_details/Authentication.rst
+++ b/docs/tech_details/Authentication.rst
@@ -89,3 +89,11 @@ After successful authentication AppAPI sets `app_api` session key to ``true``.
$this->session->set('app_api', true);
.. note:: The Nextcloud server verifies this session key and allows **CORS protection** and **Two-Factor authentication** to be bypassed for requests coming from ExApps.
+
+For ``System`` applications additional flag is set:
+
+.. code-block:: php
+
+ $this->session->set('app_api_system', true);
+
+.. note:: The Nextcloud Server skips rate limiting for requests coming from ``System`` ExApps.
diff --git a/lib/Command/ApiScopes/ListApiScopes.php b/lib/Command/ApiScopes/ListApiScopes.php
deleted file mode 100644
index e1bef1f3..00000000
--- a/lib/Command/ApiScopes/ListApiScopes.php
+++ /dev/null
@@ -1,37 +0,0 @@
-setName('app_api:scopes:list');
- $this->setDescription('List registered API scopes');
- }
-
- protected function execute(InputInterface $input, OutputInterface $output): int {
- $scopes = $this->service->getExAppApiScopes();
- if (empty($scopes)) {
- $output->writeln('No API scopes registered');
- return 0;
- }
-
- $output->writeln('Registered API scopes:');
- foreach ($scopes as $scope) {
- $output->writeln(sprintf(' %s. %s [%s]', $scope->getScopeGroup(), $scope->getApiRoute(), $scope->getName()));
- }
- return 0;
- }
-}
diff --git a/lib/Command/ExApp/Register.php b/lib/Command/ExApp/Register.php
index 73ac16f4..5f83d694 100644
--- a/lib/Command/ExApp/Register.php
+++ b/lib/Command/ExApp/Register.php
@@ -132,7 +132,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$appInfo['port'] = $appInfo['port'] ?? $this->exAppService->getExAppFreePort();
$appInfo['secret'] = $appInfo['secret'] ?? $this->random->generate(128);
$appInfo['daemon_config_name'] = $appInfo['daemon_config_name'] ?? $daemonConfigName;
- $appInfo['api_scopes'] = array_values($this->exAppApiScopeService->mapScopeNamesToNumbers($appInfo['external-app']['scopes']));
+ $appInfo['api_scopes'] = array_values($this->exAppApiScopeService->mapScopeGroupsToNumbers($appInfo['external-app']['scopes']));
$exApp = $this->exAppService->registerExApp($appInfo);
if (!$exApp) {
$this->logger->error(sprintf('Error during registering ExApp %s.', $appId));
@@ -142,7 +142,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return 3;
}
if (count($appInfo['external-app']['scopes']) > 0) {
- if (!$this->exAppScopesService->registerExAppScopes($exApp, $this->exAppApiScopeService->mapScopeNamesToNumbers($appInfo['external-app']['scopes']))) {
+ if (!$this->exAppScopesService->registerExAppScopes($exApp, $this->exAppApiScopeService->mapScopeGroupsToNumbers($appInfo['external-app']['scopes']))) {
$this->logger->error(sprintf('Error while registering API scopes for %s.', $appId));
if ($outputConsole) {
$output->writeln(sprintf('Error while registering API scopes for %s.', $appId));
diff --git a/lib/Command/ExApp/Update.php b/lib/Command/ExApp/Update.php
index 62e741e1..515a976c 100644
--- a/lib/Command/ExApp/Update.php
+++ b/lib/Command/ExApp/Update.php
@@ -171,7 +171,7 @@ private function updateExApp(InputInterface $input, OutputInterface $output, str
}
}
- $appInfo['api_scopes'] = array_values($this->exAppApiScopeService->mapScopeNamesToNumbers($appInfo['external-app']['scopes']));
+ $appInfo['api_scopes'] = array_values($this->exAppApiScopeService->mapScopeGroupsToNumbers($appInfo['external-app']['scopes']));
if (!$this->exAppService->updateExAppInfo($exApp, $appInfo)) {
$this->logger->error(sprintf('Failed to update ExApp %s info', $appId));
if ($outputConsole) {
@@ -244,7 +244,7 @@ private function updateExApp(InputInterface $input, OutputInterface $output, str
return $exAppScope->getScopeGroup();
}, $this->exAppScopeService->getExAppScopes($exApp));
// Prepare for prompt of newly requested ExApp scopes
- $requiredScopes = $this->compareExAppScopes($currentExAppScopes, $appInfo['external-app']['scopes']);
+ $requiredScopes = array_values(array_diff($this->exAppApiScopeService->mapScopeGroupsToNumbers($appInfo['external-app']['scopes']), $currentExAppScopes));
$forceScopes = (bool) $input->getOption('force-scopes');
$confirmScopes = $forceScopes;
@@ -269,7 +269,7 @@ private function updateExApp(InputInterface $input, OutputInterface $output, str
}
if (!$this->exAppScopeService->registerExAppScopes(
- $exApp, $this->exAppApiScopeService->mapScopeNamesToNumbers($appInfo['external-app']['scopes']))
+ $exApp, $this->exAppApiScopeService->mapScopeGroupsToNumbers($appInfo['external-app']['scopes']))
) {
$this->logger->error(sprintf('Failed to update ExApp %s scopes.', $appId));
if ($outputConsole) {
@@ -293,15 +293,4 @@ private function updateExApp(InputInterface $input, OutputInterface $output, str
}
return 0;
}
-
- /**
- * Compare ExApp scopes and return difference (new requested)
- *
- * @param array $currentExAppScopes
- * @param array $newExAppScopes
- * @return array
- */
- private function compareExAppScopes(array $currentExAppScopes, array $newExAppScopes): array {
- return array_values(array_diff($this->exAppApiScopeService->mapScopeNamesToNumbers($newExAppScopes), $currentExAppScopes));
- }
}
diff --git a/lib/Db/ExAppApiScope.php b/lib/Db/ExAppApiScope.php
deleted file mode 100644
index 89647783..00000000
--- a/lib/Db/ExAppApiScope.php
+++ /dev/null
@@ -1,65 +0,0 @@
-addType('apiRoute', 'string');
- $this->addType('scopeGroup', 'int');
- $this->addType('name', 'string');
- $this->addType('userCheck', 'int');
-
- if (isset($params['id'])) {
- $this->setId($params['id']);
- }
- if (isset($params['api_route'])) {
- $this->setApiRoute($params['api_route']);
- }
- if (isset($params['scope_group'])) {
- $this->setScopeGroup($params['scope_group']);
- }
- if (isset($params['name'])) {
- $this->setName($params['name']);
- }
- if (isset($params['user_check'])) {
- $this->setUserCheck($params['user_check']);
- }
- }
-
- public function jsonSerialize(): array {
- return [
- 'id' => $this->getId(),
- 'api_route' => $this->getApiRoute(),
- 'scope_group' => $this->getScopeGroup(),
- 'name' => $this->getName(),
- 'user_check' => $this->getUserCheck(),
- ];
- }
-}
diff --git a/lib/Db/ExAppApiScopeMapper.php b/lib/Db/ExAppApiScopeMapper.php
deleted file mode 100644
index a86bc21a..00000000
--- a/lib/Db/ExAppApiScopeMapper.php
+++ /dev/null
@@ -1,49 +0,0 @@
-
- */
-class ExAppApiScopeMapper extends QBMapper {
- public function __construct(IDBConnection $db) {
- parent::__construct($db, 'ex_apps_api_scopes');
- }
-
- /**
- * @throws Exception
- */
- public function findAll(int $limit = null, int $offset = null): array {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->tableName)
- ->setMaxResults($limit)
- ->setFirstResult($offset);
- return $this->findEntities($qb);
- }
-
- /**
- * @param string $apiRoute
- *
- * @throws Exception
- * @throws DoesNotExistException
- * @throws MultipleObjectsReturnedException
- *
- * @return ExAppApiScope|null
- */
- public function findByApiRoute(string $apiRoute): ?ExAppApiScope {
- $qb = $this->db->getQueryBuilder();
- $qb->select('*')
- ->from($this->tableName)
- ->where($qb->expr()->eq('api_route', $qb->createNamedParameter($apiRoute)));
- return $this->findEntity($qb);
- }
-}
diff --git a/lib/Migration/DataInitializationStep.php b/lib/Migration/DataInitializationStep.php
index 14c26976..d57b8098 100644
--- a/lib/Migration/DataInitializationStep.php
+++ b/lib/Migration/DataInitializationStep.php
@@ -5,14 +5,12 @@
namespace OCA\AppAPI\Migration;
use OCA\AppAPI\DeployActions\AIODockerActions;
-use OCA\AppAPI\Service\ExAppApiScopeService;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class DataInitializationStep implements IRepairStep {
public function __construct(
- private readonly ExAppApiScopeService $service,
private readonly AIODockerActions $AIODockerActions,
) {
}
@@ -22,12 +20,6 @@ public function getName(): string {
}
public function run(IOutput $output): void {
- if ($this->service->registerInitScopes()) {
- $output->info('API scopes successfully initialized');
- } else {
- $output->warning('Failed to initialize API scopes');
- }
-
// If in AIO - automatically register default DaemonConfig
if ($this->AIODockerActions->isAIO()) {
$output->info('AIO installation detected. Registering default daemon');
diff --git a/lib/Migration/Version2204Date20240403125002.php b/lib/Migration/Version2204Date20240403125002.php
new file mode 100644
index 00000000..3da9a327
--- /dev/null
+++ b/lib/Migration/Version2204Date20240403125002.php
@@ -0,0 +1,30 @@
+hasTable('ex_apps_api_scopes')) {
+ $schema->dropTable('ex_apps_api_scopes');
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/Service/AppAPIService.php b/lib/Service/AppAPIService.php
index 79a1a5bf..e14f8f05 100644
--- a/lib/Service/AppAPIService.php
+++ b/lib/Service/AppAPIService.php
@@ -285,8 +285,8 @@ public function validateExAppRequestToNC(IRequest $request, bool $isDav = false)
}
// BASIC ApiScope is granted to all ExApps (all API routes with BASIC scope group).
- if ($apiScope->getScopeGroup() !== ExAppApiScopeService::BASIC_API_SCOPE) {
- if (!$this->exAppScopesService->passesScopeCheck($exApp, $apiScope->getScopeGroup())) {
+ if ($apiScope['scope_group'] !== ExAppApiScopeService::BASIC_API_SCOPE) {
+ if (!$this->exAppScopesService->passesScopeCheck($exApp, $apiScope['scope_group'])) {
$this->logger->error(sprintf('ExApp %s not passed scope group check %s', $exApp->getAppid(), $path));
return false;
}
@@ -294,7 +294,7 @@ public function validateExAppRequestToNC(IRequest $request, bool $isDav = false)
}
// For APIs that not assuming work under user context we do not check ExApp users
- if ((!$exApp->getIsSystem()) && (($apiScope === null) or ($apiScope->getUserCheck()))) {
+ if ((!$exApp->getIsSystem()) && (($apiScope === null) or ($apiScope['user_check']))) {
try {
if (!$this->exAppUsersService->exAppUserExists($exApp->getAppid(), $userId)) {
$this->logger->error(sprintf('ExApp %s user %s does not exist', $exApp->getAppid(), $userId));
diff --git a/lib/Service/ExAppApiScopeService.php b/lib/Service/ExAppApiScopeService.php
index 49ca98b8..c0903ddf 100644
--- a/lib/Service/ExAppApiScopeService.php
+++ b/lib/Service/ExAppApiScopeService.php
@@ -5,84 +5,17 @@
namespace OCA\AppAPI\Service;
use OCA\AppAPI\AppInfo\Application;
-use OCA\AppAPI\Db\ExAppApiScope;
-use OCA\AppAPI\Db\ExAppApiScopeMapper;
-
-use OCP\AppFramework\Db\DoesNotExistException;
-use OCP\AppFramework\Db\MultipleObjectsReturnedException;
-use OCP\DB\Exception;
-use OCP\ICache;
-use OCP\ICacheFactory;
-use Psr\Log\LoggerInterface;
class ExAppApiScopeService {
public const BASIC_API_SCOPE = 1;
public const ALL_API_SCOPE = 9999;
- private ICache $cache;
+
+ protected array $apiScopes;
public function __construct(
- private readonly LoggerInterface $logger,
- private readonly ExAppApiScopeMapper $mapper,
- ICacheFactory $cacheFactory,
) {
- $this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_apps_api_scopes');
- }
-
- public function getExAppApiScopes(): array {
- try {
- $cacheKey = '/all_api_scopes';
- $cached = $this->cache->get($cacheKey);
- if ($cached !== null) {
- return array_map(function ($cachedEntry) {
- return $cachedEntry instanceof ExAppApiScope ? $cachedEntry : new ExAppApiScope($cachedEntry);
- }, $cached);
- }
-
- $apiScopes = $this->mapper->findAll();
- $this->cache->set($cacheKey, $apiScopes);
- return $apiScopes;
- } catch (Exception $e) {
- $this->logger->error(sprintf('Failed to get all ApiScopes. Error: %s', $e->getMessage()), ['exception' => $e]);
- return [];
- }
- }
-
- public function getApiScopeByRoute(string $apiRoute): ?ExAppApiScope {
- try {
- $cacheKey = '/api_scope_' . $apiRoute;
- $cached = $this->cache->get($cacheKey);
- if ($cached !== null) {
- return $cached instanceof ExAppApiScope ? $cached : new ExAppApiScope($cached);
- }
-
- $apiScopes = $this->getExAppApiScopes();
- foreach ($apiScopes as $apiScope) {
- if (str_starts_with($this->sanitizeOcsRoute($apiRoute), $apiScope->getApiRoute())) {
- $this->cache->set($cacheKey, $apiScope);
- return $apiScope;
- }
- }
- return null;
- } catch (Exception) {
- return null;
- }
- }
-
- /**
- * Check if the given route has ocs prefix and cut it off
- */
- private function sanitizeOcsRoute(string $route): string {
- if (preg_match("/\/ocs\/v(1|2)\.php/", $route, $matches)) {
- return str_replace($matches[0], '', $route);
- }
- return $route;
- }
-
- public function registerInitScopes(): bool {
- // Note: "user_check" should be zero for APIs that does not need user context.
$aeApiV1Prefix = '/apps/' . Application::APP_ID . '/api/v1';
-
- $initApiScopes = [
+ $this->apiScopes = [
// AppAPI scopes
['api_route' => $aeApiV1Prefix . '/ui/files-actions-menu', 'scope_group' => 1, 'name' => 'BASIC', 'user_check' => 0],
['api_route' => $aeApiV1Prefix . '/ui/top-menu', 'scope_group' => 1, 'name' => 'BASIC', 'user_check' => 0],
@@ -126,52 +59,25 @@ public function registerInitScopes(): bool {
//ALL Scope
['api_route' => 'non-exist-all-api-route', 'scope_group' => self::ALL_API_SCOPE, 'name' => 'ALL', 'user_check' => 1],
];
+ }
- $this->cache->clear('/all_api_scopes');
- $registeredApiScopes = $this->getExAppApiScopes();
- $registeredApiScopesRoutes = [];
- foreach ($registeredApiScopes as $registeredApiScope) {
- $registeredApiScopesRoutes[$registeredApiScope->getApiRoute()] = $registeredApiScope->getId();
- }
- try {
- foreach ($initApiScopes as $apiScope) {
- if (in_array($apiScope['api_route'], array_keys($registeredApiScopesRoutes))) {
- $apiScope['id'] = $registeredApiScopesRoutes[$apiScope['api_route']];
- }
- $registeredApiScope = $this->mapper->insertOrUpdate(new ExAppApiScope($apiScope));
- $cacheKey = '/api_scope_' . $apiScope['api_route'];
- $this->cache->set($cacheKey, $registeredApiScope);
+ public function getApiScopeByRoute(string $apiRoute): ?array {
+ foreach ($this->apiScopes as $apiScope) {
+ if (str_starts_with($this->sanitizeOcsRoute($apiRoute), $apiScope['api_route'])) {
+ return $apiScope;
}
- return true;
- } catch (Exception $e) {
- $this->logger->error('Failed to fill init ApiScopes: ' . $e->getMessage(), ['exception' => $e]);
- return false;
}
+ return null;
}
- public function registerApiScope(string $apiRoute, int $scopeGroup, string $name): ?ExAppApiScope {
- try {
- $apiScope = new ExAppApiScope([
- 'api_route' => $apiRoute,
- 'scope_group' => $scopeGroup,
- 'name' => $name,
- ]);
- try {
- $exAppApiScope = $this->mapper->findByApiRoute($apiRoute);
- if ($exAppApiScope !== null) {
- $apiScope->setId($exAppApiScope->getId());
- }
- } catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
- $exAppApiScope = null;
- }
- $this->mapper->insertOrUpdate($apiScope);
- $this->cache->remove('/api_scope_' . $apiRoute);
- $this->cache->remove('/all_api_scopes');
- return $apiScope;
- } catch (Exception $e) {
- $this->logger->error('Failed to register API scope: ' . $e->getMessage(), ['exception' => $e]);
- return null;
+ /**
+ * Check if the given route has ocs prefix and cut it off
+ */
+ private function sanitizeOcsRoute(string $route): string {
+ if (preg_match("/\/ocs\/v(1|2)\.php/", $route, $matches)) {
+ return str_replace($matches[0], '', $route);
}
+ return $route;
}
/**
@@ -180,20 +86,20 @@ public function registerApiScope(string $apiRoute, int $scopeGroup, string $name
* @return string[]
*/
public function mapScopeGroupsToNames(array $scopeGroups): array {
- $apiScopes = array_values(array_filter($this->getExAppApiScopes(), function (ExAppApiScope $apiScope) use ($scopeGroups) {
- return in_array($apiScope->getScopeGroup(), $scopeGroups);
+ $apiScopes = array_values(array_filter($this->apiScopes, function (array $apiScope) use ($scopeGroups) {
+ return in_array($apiScope['scope_group'], $scopeGroups);
}));
- return array_unique(array_map(function (ExAppApiScope $apiScope) {
- return $apiScope->getName();
+ return array_unique(array_map(function (array $apiScope) {
+ return $apiScope['name'];
}, $apiScopes));
}
- public function mapScopeNamesToNumbers(array $scopeNames): array {
- $apiScopes = array_values(array_filter($this->getExAppApiScopes(), function (ExAppApiScope $apiScope) use ($scopeNames) {
- return in_array($apiScope->getName(), $scopeNames);
+ public function mapScopeGroupsToNumbers(array $scopeGroups): array {
+ $apiScopes = array_values(array_filter($this->apiScopes, function (array $apiScope) use ($scopeGroups) {
+ return in_array($apiScope['name'], $scopeGroups);
}));
- return array_unique(array_map(function (ExAppApiScope $apiScope) {
- return $apiScope->getScopeGroup();
+ return array_unique(array_map(function (array $apiScope) {
+ return $apiScope['scope_group'];
}, $apiScopes));
}
}