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
7 changes: 7 additions & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ Those groups of people can then be used by any other app for sharing purpose.
<command>OCA\Circles\Command\MembersRemove</command>

<command>OCA\Circles\Command\MigrateCustomGroups</command>

<command>OCA\Circles\Command\CirclesScimSync</command>
<command>OCA\Circles\Command\CirclesModeratorsDiscover</command>
<command>OCA\Circles\Command\CirclesModeratorsSync</command>
<command>OCA\Circles\Command\CirclesOidcSync</command>
</commands>

<activity>
Expand All @@ -100,5 +105,7 @@ Those groups of people can then be used by any other app for sharing purpose.

<settings>
<admin>OCA\Circles\Settings\Admin</admin>
<personal>OCA\Circles\Settings\Personal</personal>
<personal-section>OCA\Circles\Settings\PersonalSection</personal-section>
</settings>
</info>
4 changes: 4 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@
['name' => 'Remote#member', 'url' => '/member/{type}/{userId}/', 'verb' => 'GET'],
['name' => 'Remote#inherited', 'url' => '/inherited/{circleId}/', 'verb' => 'GET'],
['name' => 'Remote#memberships', 'url' => '/memberships/{circleId}/', 'verb' => 'GET'],
['name' => 'Remote#moderator', 'url' => '/moderator/', 'verb' => 'POST'],

['name' => 'Oidc#connect', 'url' => '/oidc/connect', 'verb' => 'GET'],
['name' => 'Oidc#callback', 'url' => '/oidc/callback', 'verb' => 'GET'],

['name' => 'Deprecated#listing', 'url' => '/listing', 'verb' => 'GET'],
]
Expand Down
3 changes: 2 additions & 1 deletion lib/AppInfo/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ private function getCapabilitiesCircleConstants(): array {
Circle::CFG_ROOT => $this->l10n->t('Root'),
Circle::CFG_CIRCLE_INVITE => $this->l10n->t('Team invite'),
Circle::CFG_FEDERATED => $this->l10n->t('Federated'),
Circle::CFG_MOUNTPOINT => $this->l10n->t('Mount point')
Circle::CFG_MOUNTPOINT => $this->l10n->t('Mount point'),
Circle::CFG_THIRD_PARTY => $this->l10n->t('Third party')
],
'source'
=> [
Expand Down
37 changes: 37 additions & 0 deletions lib/Command/CirclesModeratorsDiscover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Command;

use OC\Core\Command\Base;
use OCA\Circles\Service\RemoteModCircleService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CirclesModeratorsDiscover extends Base {
public function __construct(
private readonly RemoteModCircleService $remoteModCircleService,
) {
parent::__construct();
}

protected function configure() {
parent::configure();
$this->setName('circles:moderators:discover')
->setDescription('discover the moderator circle id for each configured remote instance');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->remoteModCircleService->discoverModeratorCircles();

$output->writeln('<info>done</info>');

return 0;
}
}
37 changes: 37 additions & 0 deletions lib/Command/CirclesModeratorsSync.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Command;

use OC\Core\Command\Base;
use OCA\Circles\Service\RemoteModCircleService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CirclesModeratorsSync extends Base {
public function __construct(
private readonly RemoteModCircleService $remoteModCircleService,
) {
parent::__construct();
}

protected function configure() {
parent::configure();
$this->setName('circles:moderators:sync')
->setDescription('add moderators from each configured remote instance into every third-party circle');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->remoteModCircleService->syncModeratorCircles();

$output->writeln('<info>done</info>');

return 0;
}
}
37 changes: 37 additions & 0 deletions lib/Command/CirclesOidcSync.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Command;

use OC\Core\Command\Base;
use OCA\Circles\Service\OidcService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CirclesOidcSync extends Base {
public function __construct(
private readonly OidcService $oidcService,
) {
parent::__construct();
}

protected function configure() {
parent::configure();
$this->setName('circles:oidc:sync')
->setDescription('fetch memberships from OIDC server and add users to corresponding circles if not a member');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->oidcService->syncMemberships();

$output->writeln('<info>done</info>');

return 0;
}
}
37 changes: 37 additions & 0 deletions lib/Command/CirclesScimSync.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Command;

use OC\Core\Command\Base;
use OCA\Circles\Service\ScimService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CirclesScimSync extends Base {
public function __construct(
private readonly ScimService $scimService,
) {
parent::__construct();
}

protected function configure() {
parent::configure();
$this->setName('circles:scim:sync')
->setDescription('fetch circles from SCIM server and create the corresponding circles if missing');
}

protected function execute(InputInterface $input, OutputInterface $output): int {
$this->scimService->syncCircles();

$output->writeln('<info>done</info>');

return 0;
}
}
39 changes: 39 additions & 0 deletions lib/ConfigLexicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,27 @@ class ConfigLexicon implements ILexicon {
public const FEDERATED_TEAMS_FRONTAL = 'federated_teams_frontal';
public const REMOVE_SHARE_TOKENS_DONE = 'remove_share_tokens_done';

// OIDC
public const OIDC_ENABLED = 'oidc_enabled';
public const OIDC_ISSUER = 'oidc_issuer';
public const OIDC_CLIENT_ID = 'oidc_client_id';
public const OIDC_CLIENT_SECRET = 'oidc_client_secret';
public const OIDC_AUTHORIZATION_ENDPOINT = 'oidc_authorization_endpoint';
public const OIDC_TOKEN_ENDPOINT = 'oidc_token_endpoint';
public const OIDC_USERINFO_ENDPOINT = 'oidc_userinfo_endpoint';
public const OIDC_SCOPE = 'oidc_scope';
public const OIDC_MEMBERSHIP_CLAIM = 'oidc_membership_claim';

// SCIM
public const SCIM_ENABLED = 'scim_enabled';
public const SCIM_ENDPOINT = 'scim_endpoint';
public const SCIM_TOKEN = 'scim_token';

// Remote moderator circle
public const REMOTE_MOD_CIRCLE_INSTANCES = 'remote_mod_circle_instances'; // without http/https
public const REMOTE_MOD_CIRCLE_MAPPING = 'remote_mod_circle_mapping';
public const REMOTE_MOD_CIRCLE_LOCAL_ID = 'remote_mod_circle_local_id';

public function getStrictness(): Strictness {
return Strictness::IGNORE;
}
Expand All @@ -34,6 +55,24 @@ public function getAppConfigs(): array {
new Entry(key: self::FEDERATED_TEAMS_ENABLED, type: ValueType::BOOL, defaultRaw: false, definition: 'disable/enable Federated Teams', lazy: true),
new Entry(key: self::FEDERATED_TEAMS_FRONTAL, type: ValueType::STRING, defaultRaw: '', definition: 'domain name used to auth public request', lazy: true),
new Entry(key: self::REMOVE_SHARE_TOKENS_DONE, type: ValueType::BOOL, defaultRaw: false, definition: 'whether the remove share tokens repair step has already been executed', lazy: true),
// OIDC
new Entry(key: self::OIDC_ENABLED, type: ValueType::BOOL, defaultRaw: false, definition: 'disable/enable OIDC integration', lazy: true),
new Entry(key: self::OIDC_ISSUER, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC provider issuer URL', lazy: true),
new Entry(key: self::OIDC_CLIENT_ID, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC client id', lazy: true),
new Entry(key: self::OIDC_CLIENT_SECRET, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC client secret', lazy: true),
new Entry(key: self::OIDC_AUTHORIZATION_ENDPOINT, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC authorization endpoint', lazy: true),
new Entry(key: self::OIDC_TOKEN_ENDPOINT, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC token endpoint', lazy: true),
new Entry(key: self::OIDC_USERINFO_ENDPOINT, type: ValueType::STRING, defaultRaw: '', definition: 'OIDC userinfo endpoint', lazy: true),
new Entry(key: self::OIDC_SCOPE, type: ValueType::STRING, defaultRaw: 'openid', definition: 'OIDC scope(s) requested during authorization', lazy: true),
new Entry(key: self::OIDC_MEMBERSHIP_CLAIM, type: ValueType::STRING, defaultRaw: '', definition: 'claim name containing group membership information', lazy: true),
// SCIM
new Entry(key: self::SCIM_ENABLED, type: ValueType::BOOL, defaultRaw: false, definition: 'disable/enable SCIM integration', lazy: true),
new Entry(key: self::SCIM_ENDPOINT, type: ValueType::STRING, defaultRaw: '', definition: 'SCIM server endpoint for group discovery', lazy: true),
new Entry(key: self::SCIM_TOKEN, type: ValueType::STRING, defaultRaw: '', definition: 'bearer token used to authenticate against the SCIM server', lazy: true),
// Remote moderator circle
new Entry(key: self::REMOTE_MOD_CIRCLE_INSTANCES, type: ValueType::ARRAY, defaultRaw: [], definition: 'list of remote instances to sync a moderator circle from', lazy: true),
new Entry(key: self::REMOTE_MOD_CIRCLE_MAPPING, type: ValueType::ARRAY, defaultRaw: [], definition: 'map of instance => circle id for known remote moderator circles', lazy: true),
new Entry(key: self::REMOTE_MOD_CIRCLE_LOCAL_ID, type: ValueType::STRING, defaultRaw: '', definition: 'circle id of the local circle acting as a moderator in remote circles', lazy: true),
];
}

Expand Down
Loading
Loading