Skip to content
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

Caching adjustments. Refactoring, use service layers with caching #14

Merged
merged 29 commits into from
Jul 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f08aa4d
Caching adjustments. Refactoring, use service layer with cache for ot…
andrey18106 Jul 17, 2023
aea71cd
fix missing return
andrey18106 Jul 18, 2023
79adf39
Added lint, psalm, fixes. last_response_time->last_check_time
andrey18106 Jul 18, 2023
2bf9acc
updated jobs matrix
andrey18106 Jul 18, 2023
d73fac6
ci fixes
andrey18106 Jul 18, 2023
e044ad0
ci fixes
andrey18106 Jul 18, 2023
a0f176a
ci fixes
andrey18106 Jul 18, 2023
3c7a941
exApp caching fixes
andrey18106 Jul 18, 2023
f502e36
delete query fixes
andrey18106 Jul 18, 2023
9d74779
Added container healthcheck. Updated daemon list command
andrey18106 Jul 19, 2023
b66aa93
Minor caches fixes
andrey18106 Jul 19, 2023
5be6b59
Added job with redis
andrey18106 Jul 19, 2023
62bf6f5
Added php redis module
andrey18106 Jul 19, 2023
c708477
fix redis config setup
andrey18106 Jul 19, 2023
e64b61e
fix redis service host
andrey18106 Jul 19, 2023
8dc4793
fix check of redis keys
andrey18106 Jul 19, 2023
af8e5d8
php-cs fixes
andrey18106 Jul 19, 2023
1ba6df2
fix redis service name
andrey18106 Jul 19, 2023
ac6d4a5
last_response_time -> last_check_time (nc_py_api getExApps)
andrey18106 Jul 20, 2023
b0f3adb
fix xml file loading
andrey18106 Jul 20, 2023
8bf9d4d
Refactoring. Removed unused parts
andrey18106 Jul 20, 2023
cb64c63
minor fixes
andrey18106 Jul 20, 2023
bf3a311
file action fix
andrey18106 Jul 21, 2023
0233af6
cs fix
andrey18106 Jul 21, 2023
125acee
fix unregister file action
andrey18106 Jul 21, 2023
cd0cdf8
added return not_found status
andrey18106 Jul 21, 2023
f2ef0fd
fix psalm
andrey18106 Jul 21, 2023
723f08d
fix ocs not_found
andrey18106 Jul 21, 2023
1337107
to return `OCSNotFoundException` in `unregisterFileActionMenu`
bigcat88 Jul 22, 2023
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ dock-sock27:
dock-sock26:
@echo "creating daemon for nextcloud 'stable26' container"
docker exec master-nextcloud-1 sudo -u www-data php occ app_ecosystem_v2:daemon:unregister docker_dev || true
docker exec master-stable6-1 sudo -u www-data php occ app_ecosystem_v2:daemon:register \
docker exec master-stable26-1 sudo -u www-data php occ app_ecosystem_v2:daemon:register \
docker_dev Docker docker-install unix-socket /var/run/docker.sock http://stable26/index.php --net=master_default

.PHONY: dock2port
Expand Down
77 changes: 2 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ This authentication is based on a shared secret between Nextcloud and the extern

1. ExApp sends a request to Nextcloud
2. Nextcloud passes request to AppEcosystemV2
3. AppEcosystemV2 validates request (see [authentication](#AppEcosystemV2-authentication) section)
3. AppEcosystemV2 validates request
4. Request is accepted/rejected

```mermaid
Expand All @@ -61,80 +61,7 @@ sequenceDiagram
Nextcloud-->>-ExApp: Response (200/401)
```

### AppEcosystemV2 authentication

Each ExApp request to secured with AEAuth must contain the following headers (order is important):

1. `AE-VERSION` - `[required]` minimal version of the AppEcosystemV2
2. `EX-APP-ID` - `[required]` id of the ExApp
3. `EX-APP-VERSION` - `[required]` version of the ExApp
4. `NC-USER-ID` - `[optional]` the user under which the request is made, can be empty in case of system apps (more details in [scopes](#AppEcosystemV2-scopes) section)
5. `AE-DATA-HASH` - `[required]` hash of the request body (see details in [signature](#AE-SIGNATURE) section)
6. `AE-SIGN-TIME` - `[required]` unix timestamp of the request
7. `AE-SIGNATURE` - `[required]` signature of the request (see details [signature](#AE-SIGNATURE) section)

### AE-SIGNATURE

AppEcosystemV2 signature (AE-SIGNATURE) is a HMAC-SHA256 hash of the request signed with the shared secret.

Depending on request method signing body is different:

* `GET`
* method
* uri (with urlencoded query params)
* headers (`AE-VERSION`, `EX-APP-ID`, `EX-APP-VERSION`, `NC-USER-ID`, `AE-DATA-HASH`, `AE-SIGN-TIME`)
* Others
* method
* uri (with urlencoded query params)
* headers (`AE-VERSION`, `EX-APP-ID`, `EX-APP-VERSION`, `NC-USER-ID`, `AE-DATA-HASH`, `AE-SIGN-TIME`)
* xxh64 hash from request body (post data, json, files, etc)

### AE-DATA-HASH signature

`AE-DATA-HASH` header must contain a xxh64 hash of the request body.
It's calculated even if the request body is empty (e.g. empty hash: `ef46db3751d8e999`).

### AppEcosystemV2 scopes

AppEcosystemV2 supports the following default scopes:

* `BASIC_API_SCOPE` - init scope, used when ExApp is on initialization step and has no user context
* `SYSTEM_API_SCOPE` - configured for system apps, mostly has no user context
* `DAV_API_SCOPE` - scope for dav requests, has user context

### AppEcosystemV2 authentication diagram

```mermaid
sequenceDiagram
autonumber
participant ExApp
box Nextcloud
participant Nextcloud
participant AppEcosystemV2
end
ExApp->>+Nextcloud: Request to API
Nextcloud->>Nextcloud: Check if AE-SIGNATURE header exists
Nextcloud-->>ExApp: Reject if AE-SIGNATURE header not exists
Nextcloud->>Nextcloud: Check if AppEcosystemV2 enabled
Nextcloud-->>ExApp: Reject if AppEcosystemV2 not enabled
Nextcloud->>+AppEcosystemV2: Validate request
AppEcosystemV2-->>AppEcosystemV2: Check if ExApp exists and enabled
AppEcosystemV2-->>Nextcloud: Reject if ExApp not exists or disabled
AppEcosystemV2-->>AppEcosystemV2: Validate AE-SIGN-TIME
AppEcosystemV2-->>Nextcloud: Reject if sign time diff > 5 min
AppEcosystemV2-->>AppEcosystemV2: Generate and validate AE-SIGNATURE
AppEcosystemV2-->>Nextcloud: Reject if signature not match
AppEcosystemV2-->>AppEcosystemV2: Validate AE-DATA-HASH
AppEcosystemV2-->>Nextcloud: Reject if data hash not match
AppEcosystemV2-->>AppEcosystemV2: Check API scope
AppEcosystemV2-->>Nextcloud: Reject if API scope not match
AppEcosystemV2-->>AppEcosystemV2: Check if user interacted with ExApp
AppEcosystemV2-->>Nextcloud: Reject if user has not interacted with ExApp (attempt to bypass user)
AppEcosystemV2-->>AppEcosystemV2: Check if user is not empty and active
AppEcosystemV2-->>Nextcloud: Set active user
AppEcosystemV2->>-Nextcloud: Request accepted/rejected
Nextcloud->>-ExApp: Response (200/401)
```
More details in [docs](https://cloud-py-api.github.io/app_ecosystem_v2/authentication.html)

## 🔧 Configuration

Expand Down
3 changes: 3 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@
<command>OCA\AppEcosystemV2\Command\ExApp\Enable</command>
<command>OCA\AppEcosystemV2\Command\ExApp\Disable</command>
<command>OCA\AppEcosystemV2\Command\ExApp\ListExApps</command>
<command>OCA\AppEcosystemV2\Command\ExApp\Scopes\ListScopes</command>
<command>OCA\AppEcosystemV2\Command\ExApp\Users\ListUsers</command>
<command>OCA\AppEcosystemV2\Command\ExAppConfig\GetConfig</command>
<command>OCA\AppEcosystemV2\Command\ExAppConfig\SetConfig</command>
<command>OCA\AppEcosystemV2\Command\ExAppConfig\DeleteConfig</command>
<command>OCA\AppEcosystemV2\Command\ExAppConfig\ListConfig</command>
<command>OCA\AppEcosystemV2\Command\Daemon\RegisterDaemon</command>
<command>OCA\AppEcosystemV2\Command\Daemon\UnregisterDaemon</command>
<command>OCA\AppEcosystemV2\Command\Daemon\ListDaemons</command>
<command>OCA\AppEcosystemV2\Command\Scopes\ListApiScopes</command>
</commands>
<settings>
<admin>OCA\AppEcosystemV2\Settings\Admin</admin>
Expand Down
116 changes: 116 additions & 0 deletions docs/authentication.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
==============
Authentication
==============

AppEcosystemV2 adds separate authentication for external apps.
This authentication is based on a shared secret between Nextcloud and the external app.


Authentication flow
^^^^^^^^^^^^^^^^^^^

1. ExApp sends a request to Nextcloud
2. Nextcloud passes request to AppEcosystemV2
3. AppEcosystemV2 validates request (see [authentication](#AppEcosystemV2-authentication) section)
4. Request is accepted/rejected

.. mermaid::

sequenceDiagram
participant ExApp
box Nextcloud
participant Nextcloud
participant AppEcosystemV2
end
ExApp->>+Nextcloud: Request to API
Nextcloud->>+AppEcosystemV2: Validate request
AppEcosystemV2-->>-Nextcloud: Request accepted/rejected
Nextcloud-->>-ExApp: Response (200/401)


Authentication headers
^^^^^^^^^^^^^^^^^^^^^^

Each ExApp request to secured API with AppEcosystemAuth must contain the following headers (order is important):

1. `AE-VERSION` - minimal version of the AppEcosystemV2
2. `EX-APP-ID` - id of the ExApp
3. `EX-APP-VERSION` - version of the ExApp
4. `NC-USER-ID` - the user under which the request is made, can be empty in case of system apps (more details in [scopes](#AppEcosystemV2-scopes) section)
5. `AE-DATA-HASH` - hash of the request body (see details in [signature](#AE-SIGNATURE) section)
6. `AE-SIGN-TIME` - unix timestamp of the request
7. `AE-SIGNATURE` - signature of the request (see details [signature](#AE-SIGNATURE) section)


AE_SIGNATURE
************

AppEcosystemV2 signature (AE-SIGNATURE) is a HMAC-SHA256 hash of the request signed with the shared secret.

The signature is calculated from the following data:

* method
* uri (with urlencoded query parameters)
* headers (`AE-VERSION`, `EX-APP-ID`, `EX-APP-VERSION`, `NC-USER-ID`, `AE-DATA-HASH`, `AE-SIGN-TIME`)
* xxh64 hash from request body (post data, json, files, etc.)

AE_DATA_HASH
************

`AE-DATA-HASH` header must contain a xxh64 hash of the request body.
It's calculated even if the request body is empty (e.g. empty hash: `ef46db3751d8e999`).


ExApp scopes
************

AppEcosystemV2 will support extensible scopes (with interfaces to register own ones).
Currently, the following scopes are available:

* `BASIC`
* `SYSTEM`
* `USER_INFO`
* `USER_STATUS`
* `NOTIFICATIONS`
* `WEATHER_STATUS`
* `DAV`

There is a CLI command to list registered scopes: `occ app_ecosystem_v2:scopes:list`.

Authentication flow in details
******************************

.. mermaid::
:zoom:

sequenceDiagram
autonumber
participant ExApp
box Nextcloud
participant Nextcloud
participant AppEcosystemV2
end
ExApp->>+Nextcloud: Request to API
Nextcloud->>Nextcloud: Check if AE-SIGNATURE header exists
Nextcloud-->>ExApp: Reject if AE-SIGNATURE header not exists
Nextcloud->>Nextcloud: Check if AppEcosystemV2 enabled
Nextcloud-->>ExApp: Reject if AppEcosystemV2 not enabled
Nextcloud->>+AppEcosystemV2: Validate request
AppEcosystemV2-->>AppEcosystemV2: Check if ExApp exists and enabled
AppEcosystemV2-->>Nextcloud: Reject if ExApp not exists or disabled
AppEcosystemV2-->>AppEcosystemV2: Validate AE-SIGN-TIME
AppEcosystemV2-->>Nextcloud: Reject if sign time diff > 5 min
AppEcosystemV2-->>AppEcosystemV2: Generate and validate AE-SIGNATURE
AppEcosystemV2-->>Nextcloud: Reject if signature not match
AppEcosystemV2-->>AppEcosystemV2: Validate AE-DATA-HASH
AppEcosystemV2-->>Nextcloud: Reject if data hash not match
AppEcosystemV2-->>AppEcosystemV2: Check API scope
AppEcosystemV2-->>Nextcloud: Reject if API scope not match
AppEcosystemV2-->>AppEcosystemV2: Check if user interacted with ExApp
AppEcosystemV2-->>Nextcloud: Reject if user has not interacted with ExApp (attempt to bypass user)
AppEcosystemV2-->>AppEcosystemV2: Check if user is not empty and active
AppEcosystemV2-->>Nextcloud: Set active user
AppEcosystemV2->>-Nextcloud: Request accepted/rejected
Nextcloud->>-ExApp: Response (200/401)


4 changes: 3 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"sphinx_copybutton",
"sphinx_inline_tabs",
"sphinx_issues",
"sphinx_rtd_theme"]
"sphinx_rtd_theme",
"sphinxcontrib.mermaid",
]

# General information about the project.
project = "AppEcosystemV2"
Expand Down
1 change: 1 addition & 0 deletions docs/definitions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ AppEcosystemV2 brings out the following terms:
* ExApp (External App) - the app on another (from PHP) programming language, which can be hosted separately or inside container
* DaemonConfig - configuration of orchestration daemon (e.g. Docker) where ExApps are deployed
* ExAppConfig - similar to Nextcloud `app_config`, but for ExApps configuration
* AppEcosystemAuth - AppEcosystemV2 authentication
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Welcome to the amazing docs that we will write for AppEcosystemV2!
definitions.rst
development/index.rst
deploy/index.rst
./authentication
1 change: 1 addition & 0 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ sphinx-rtd-theme>=1.2.2
sphinx-copybutton>=0.5.2
sphinx_issues>=3.0.1
sphinx-inline-tabs
sphinxcontrib-mermaid>=0.9.2
2 changes: 1 addition & 1 deletion lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
class Application extends App implements IBootstrap {
public const APP_ID = 'app_ecosystem_v2';

public const CACHE_TTL = 3600;
public const CACHE_TTL = 60 * 60;
public const ICON_CACHE_TTL = 60 * 60 *24;

public function __construct(array $urlParams = []) {
Expand Down
6 changes: 5 additions & 1 deletion lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use OCA\AppEcosystemV2\AppInfo\Application;
use OCA\AppEcosystemV2\Db\ExAppScope;
use OCA\AppEcosystemV2\Service\AppEcosystemV2Service;
use OCA\AppEcosystemV2\Service\ExAppScopesService;
use OCP\App\IAppManager;
use OCP\Capabilities\ICapability;
use OCP\IConfig;
Expand All @@ -43,18 +44,21 @@ class Capabilities implements ICapability {
private IConfig $config;
private IAppManager $appManager;
private AppEcosystemV2Service $service;
private ExAppScopesService $exAppScopesService;
private IRequest $request;

public function __construct(
IConfig $config,
IAppManager $appManager,
AppEcosystemV2Service $service,
ExAppScopesService $exAppScopesService,
IRequest $request,
) {
$this->config = $config;
$this->appManager = $appManager;
$this->service = $service;
$this->request = $request;
$this->exAppScopesService = $exAppScopesService;
}

public function getCapabilities(): array {
Expand All @@ -75,7 +79,7 @@ private function attachExAppScopes(&$capabilities): void {
if ($exApp !== null) {
$capabilities['scopes'] = array_map(function (ExAppScope $scope) {
return intval($scope->getScopeGroup());
}, $this->service->getExAppScopeGroups($exApp));
}, $this->exAppScopesService->getExAppScopes($exApp));
}
}
}
Expand Down
24 changes: 16 additions & 8 deletions lib/Command/ExApp/Register.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@

namespace OCA\AppEcosystemV2\Command\ExApp;

use OCA\AppEcosystemV2\Db\ExApp;
use OCA\AppEcosystemV2\DeployActions\DockerActions;
use OCA\AppEcosystemV2\DeployActions\ManualActions;
use OCA\AppEcosystemV2\Service\DaemonConfigService;
use OCA\AppEcosystemV2\Service\ExAppApiScopeService;
use OCP\DB\Exception;
use OCP\Http\Client\IResponse;
use Symfony\Component\Console\Command\Command;
Expand All @@ -44,21 +39,32 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

use OCA\AppEcosystemV2\Db\ExApp;
use OCA\AppEcosystemV2\DeployActions\DockerActions;
use OCA\AppEcosystemV2\DeployActions\ManualActions;
use OCA\AppEcosystemV2\Service\AppEcosystemV2Service;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use OCA\AppEcosystemV2\Service\DaemonConfigService;
use OCA\AppEcosystemV2\Service\ExAppApiScopeService;
use OCA\AppEcosystemV2\Service\ExAppScopesService;
use OCA\AppEcosystemV2\Service\ExAppUsersService;

class Register extends Command {
private AppEcosystemV2Service $service;
private DaemonConfigService $daemonConfigService;
private ExAppApiScopeService $exAppApiScopeService;
private ExAppScopesService $exAppScopesService;
private ExAppUsersService $exAppUsersService;
private DockerActions $dockerActions;
private ManualActions $manualActions;

public function __construct(
AppEcosystemV2Service $service,
DaemonConfigService $daemonConfigService,
ExAppApiScopeService $exAppApiScopeService,
ExAppScopesService $exAppScopesService,
ExAppUsersService $exAppUsersService,
DockerActions $dockerActions,
ManualActions $manualActions,
) {
Expand All @@ -67,6 +73,8 @@ public function __construct(
$this->service = $service;
$this->daemonConfigService = $daemonConfigService;
$this->exAppApiScopeService = $exAppApiScopeService;
$this->exAppScopesService = $exAppScopesService;
$this->exAppUsersService = $exAppUsersService;

// TODO: Change to dynamic DeployActions resolving
$this->dockerActions = $dockerActions;
Expand Down Expand Up @@ -141,7 +149,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

if (filter_var($exAppInfo['system_app'], FILTER_VALIDATE_BOOLEAN)) {
try {
$this->service->setupSystemAppFlag($exApp);
$this->exAppUsersService->setupSystemAppFlag($exApp);
}
catch (Exception $e) {
$output->writeln(sprintf('Error while setting app system flag: %s', $e->getMessage()));
Expand Down Expand Up @@ -219,7 +227,7 @@ private function registerExAppScopes($output, ExApp $exApp, array $requestedExAp
$scopeType = $required ? 'required' : 'optional';
$registeredScopeGroups = [];
foreach ($requestedExAppScopeGroups as $scopeGroup) {
if ($this->service->setExAppScopeGroup($exApp, $scopeGroup)) {
if ($this->exAppScopesService->setExAppScopeGroup($exApp, $scopeGroup)) {
$registeredScopeGroups[] = $scopeGroup;
} else {
$output->writeln(sprintf('Failed to set %s ExApp scope group: %s', $scopeType, $scopeGroup));
Expand Down
Loading
Loading