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

ExApp version check #29

Merged
merged 10 commits into from
Aug 9, 2023
Merged
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
145 changes: 145 additions & 0 deletions .github/workflows/tests-special.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
name: Tests Special

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: tests-special-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
app-version-higher:
runs-on: ubuntu-22.04
name: ExApp version higher
env:
NEXTCLOUD_URL: "http://localhost:8080/"
APP_ID: "nc_py_api"
APP_PORT: 9009
APP_VERSION: "1.0.0"
APP_SECRET: "tC6vkwPhcppjMykD1r0n9NlI95uJMBYjs5blpIcA1PAdoPDmc5qoAjaBAkyocZ6E"

services:
postgres:
image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest
ports:
- 4444:5432/tcp
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5

steps:
- uses: actions/setup-python@v4
with:
python-version: '3.11'

- name: Set app env
run: echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV

- name: Checkout server
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
submodules: true
repository: nextcloud/server
ref: 'stable27'

- name: Checkout Notifications
uses: actions/checkout@v3
with:
repository: nextcloud/notifications
ref: ${{ matrix.server-version }}
path: apps/notifications

- name: Checkout AppEcosystemV2
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
with:
path: apps/${{ env.APP_NAME }}

- name: Set up php
uses: shivammathur/setup-php@4bd44f22a98a19e0950cbad5f31095157cc9621b # v2
with:
php-version: '8.1'
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql
coverage: none
ini-file: development
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check composer file existence
id: check_composer
uses: andstor/file-existence-action@20b4d2e596410855db8f9ca21e96fbe18e12930b # v2
with:
files: apps/${{ env.APP_NAME }}/composer.json

- name: Set up dependencies
if: steps.check_composer.outputs.files_exists == 'true'
working-directory: apps/${{ env.APP_NAME }}
run: composer i

- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 \
--database-port=$DB_PORT --database-user=root --database-pass=rootpassword \
--admin-user admin --admin-pass admin
./occ config:system:set allow_local_remote_servers --value true
./occ app:enable notifications
./occ app:enable --force ${{ env.APP_NAME }}
patch -p 1 -i apps/${{ env.APP_NAME }}/base_php.patch

- name: Run Nextcloud
run: php -S 127.0.0.1:8080 &

- name: Checkout NcPyApi
uses: actions/checkout@v3
with:
path: nc_py_api
repository: cloud-py-api/nc_py_api

- name: Install NcPyApi
working-directory: nc_py_api
run: python3 -m pip -v install ".[dev]"

- name: Register NcPyApi
run: |
cd nc_py_api
python3 tests/_install.py &
echo $! > /tmp/_install.pid
cd ..
sleep 5s
php occ app_ecosystem_v2:daemon:register manual_install "Manual Install" manual-install 0 0 0
php occ app_ecosystem_v2:app:register nc_py_api manual_install --json-info \
"{\"appid\":\"$APP_ID\",\"name\":\"$APP_ID\",\"daemon_config_name\":\"manual_install\",\"version\":\"$APP_VERSION\",\"secret\":\"$APP_SECRET\",\"host\":\"localhost\",\"port\":$APP_PORT,\"protocol\":\"http\",\"system_app\":1}" \
-e --force-scopes
kill -15 $(cat /tmp/_install.pid)
timeout 3m tail --pid=$(cat /tmp/_install.pid) -f /dev/null

- name: Run Manual App Update test
working-directory: apps/${{ env.APP_NAME }}
run: python3 tests/app_version_higher.py

- name: Upload NC logs
if: always()
uses: actions/upload-artifact@v3
with:
name: app_version_higher_nextcloud.log
path: data/nextcloud.log
if-no-files-found: warn

tests-success:
permissions:
contents: none
runs-on: ubuntu-22.04
needs: [app-version-higher]
name: TestsSpecial-OK
steps:
- run: echo "Tests special passed successfully"
1 change: 1 addition & 0 deletions docs/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Authentication flow in details
Nextcloud->>+AppEcosystemV2: Validate request
AppEcosystemV2-->>AppEcosystemV2: Check if ExApp exists and enabled
AppEcosystemV2-->>Nextcloud: Reject if ExApp not exists or disabled
AppEcosystemV2-->>AppEcosystemV2: Check if ExApp version changed
AppEcosystemV2-->>AppEcosystemV2: Validate AE-SIGN-TIME
AppEcosystemV2-->>Nextcloud: Reject if sign time diff > 5 min
AppEcosystemV2-->>AppEcosystemV2: Generate and validate AE-SIGNATURE
Expand Down
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OCA\AppEcosystemV2\Listener\LoadFilesPluginListener;
use OCA\AppEcosystemV2\Listener\SabrePluginAuthInitListener;
use OCA\AppEcosystemV2\Middleware\AppEcosystemAuthMiddleware;
use OCA\AppEcosystemV2\Notifications\ExAppAdminNotifier;
use OCA\AppEcosystemV2\Notifications\ExAppNotifier;
use OCA\AppEcosystemV2\Profiler\AEDataCollector;
use OCA\AppEcosystemV2\PublicCapabilities;
Expand Down Expand Up @@ -45,6 +46,7 @@ public function register(IRegistrationContext $context): void {
$context->registerMiddleware(AppEcosystemAuthMiddleware::class);
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);
$context->registerNotifierService(ExAppNotifier::class);
$context->registerNotifierService(ExAppAdminNotifier::class);
}

public function boot(IBootContext $context): void {
Expand Down
12 changes: 12 additions & 0 deletions lib/Db/ExAppMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,16 @@ public function updateLastCheckTime(ExApp $exApp): int {
$qb->expr()->eq('appid', $qb->createNamedParameter($exApp->getAppid()))
)->executeStatement();
}

/**
* @throws Exception
*/
public function updateExAppVersion(ExApp $exApp): int {
$qb = $this->db->getQueryBuilder();
return $qb->update($this->tableName)
->set('version', $qb->createNamedParameter($exApp->getVersion(), IQueryBuilder::PARAM_STR))
->where(
$qb->expr()->eq('appid', $qb->createNamedParameter($exApp->getAppid()))
)->executeStatement();
}
}
62 changes: 62 additions & 0 deletions lib/Notifications/ExAppAdminNotifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

namespace OCA\AppEcosystemV2\Notifications;

use OCA\AppEcosystemV2\AppInfo\Application;
use OCA\AppEcosystemV2\Service\AppEcosystemV2Service;
use OCP\IURLGenerator;
use OCP\L10N\IFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;

class ExAppAdminNotifier implements INotifier {
private IFactory $factory;
private IURLGenerator $url;
private AppEcosystemV2Service $service;

public function __construct(
IFactory $factory,
IURLGenerator $urlGenerator,
AppEcosystemV2Service $service,
) {
$this->factory = $factory;
$this->url = $urlGenerator;
$this->service = $service;
}

public function getID(): string {
return Application::APP_ID;
}

public function getName(): string {
return $this->factory->get(Application::APP_ID)->t('AppEcosystemV2 ExApp version update notifier');
}

public function prepare(INotification $notification, string $languageCode): INotification {
$exApp = $this->service->getExApp($notification->getApp());
// TODO: Think about another possible admin ExApp notifications, make them unified
// TODO: Think about ExApp rich objects
if ($exApp === null || $notification->getSubject() !== 'ex_app_version_update') {
throw new \InvalidArgumentException();
}
if ($exApp->getEnabled()) {
throw new \InvalidArgumentException('ExApp is probably already re-enabled');
}

$parameters = $notification->getSubjectParameters();

$notification->setLink($this->url->getAbsoluteURL('/index.php/settings/admin/app_ecosystem_v2'));
$notification->setIcon($this->url->imagePath(Application::APP_ID, 'app-dark.svg'));

if (isset($parameters['rich_subject']) && isset($parameters['rich_subject_params'])) {
$notification->setRichSubject($parameters['rich_subject'], $parameters['rich_subject_params']);
}
if (isset($parameters['rich_message']) && isset($parameters['rich_message_params'])) {
$notification->setRichMessage($parameters['rich_message'], $parameters['rich_message_params']);
}

return $notification;
}
}
5 changes: 3 additions & 2 deletions lib/Notifications/ExAppNotifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public function prepare(INotification $notification, string $languageCode): INot
if ($exApp === null) {
throw new \InvalidArgumentException();
}
// Only enabled ExApps can render notifications
if (!$exApp->getEnabled()) {
if ($notification->getSubject() === 'ex_app_version_update' && $exApp->getEnabled()) {
throw new \InvalidArgumentException('ExApp is probably already re-enabled');
} elseif (!$exApp->getEnabled()) { // Only enabled ExApps can render notifications
throw new \InvalidArgumentException('ExApp is disabled');
}

Expand Down
30 changes: 25 additions & 5 deletions lib/Notifications/ExNotificationsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

namespace OCA\AppEcosystemV2\Notifications;

use OCP\IGroupManager;
use OCP\Notification\IManager;
use OCP\Notification\INotification;

class ExNotificationsManager {
private IManager $manager;
private IManager $notificationManager;
private IGroupManager $groupManager;

public function __construct(IManager $manager) {
$this->manager = $manager;
public function __construct(IManager $manager, IGroupManager $groupManager) {
$this->notificationManager = $manager;
$this->groupManager = $groupManager;
}

/**
Expand All @@ -24,14 +27,31 @@ public function __construct(IManager $manager) {
* @return INotification
*/
public function sendNotification(string $appId, ?string $userId = null, array $params = []): INotification {
$notification = $this->manager->createNotification();
$notification = $this->notificationManager->createNotification();
$notification
->setApp($appId)
->setUser($userId)
->setDateTime(new \DateTime())
->setObject($params['object'], $params['object_id'])
->setSubject($params['subject_type'], $params['subject_params']);
$this->manager->notify($notification);
$this->notificationManager->notify($notification);
return $notification;
}

public function sendAdminsNotification(string $appId, array $params = []): array {
$admins = $this->groupManager->get("admin")->getUsers();
$notifications = [];
foreach ($admins as $adminUser) {
$notification = $this->notificationManager->createNotification();
$notification
->setApp($appId)
->setUser($adminUser->getUID())
->setDateTime(new \DateTime())
->setObject($params['object'], $params['object_id'])
->setSubject($params['subject_type'], $params['subject_params']);
$this->notificationManager->notify($notification);
$notifications[] = $notification;
}
return $notifications;
}
}
Loading