diff --git a/lib/Command/CirclesConfig.php b/lib/Command/CirclesConfig.php
index 5a823db3d..68ecfa42e 100644
--- a/lib/Command/CirclesConfig.php
+++ b/lib/Command/CirclesConfig.php
@@ -172,6 +172,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (strtolower($input->getOption('output')) === 'json') {
$output->writeln(json_encode($outcome, JSON_PRETTY_PRINT));
+ } elseif (strtolower($input->getOption('output')) !== 'none') {
+ $circle = $this->circleService->getCircle($circleId);
+ $output->writeln(
+ json_encode(
+ Circle::getCircleFlags($circle, Circle::FLAGS_LONG),
+ JSON_PRETTY_PRINT
+ )
+ );
}
return 0;
diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php
index efa78363b..fa7a454e5 100644
--- a/lib/Controller/LocalController.php
+++ b/lib/Controller/LocalController.php
@@ -31,8 +31,6 @@
namespace OCA\Circles\Controller;
-use OCA\Circles\Tools\Traits\TDeserialize;
-use OCA\Circles\Tools\Traits\TNCLogger;
use Exception;
use OCA\Circles\Exceptions\FederatedUserException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
@@ -49,7 +47,10 @@
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use OCA\Circles\Service\MembershipService;
+use OCA\Circles\Service\PermissionService;
use OCA\Circles\Service\SearchService;
+use OCA\Circles\Tools\Traits\TDeserialize;
+use OCA\Circles\Tools\Traits\TNCLogger;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCS\OCSException;
use OCP\AppFramework\OCSController;
@@ -81,6 +82,9 @@ class LocalController extends OcsController {
/** @var MembershipService */
private $membershipService;
+ /** @var PermissionService */
+ private $permissionService;
+
/** @var SearchService */
private $searchService;
@@ -109,6 +113,7 @@ public function __construct(
CircleService $circleService,
MemberService $memberService,
MembershipService $membershipService,
+ PermissionService $permissionService,
SearchService $searchService,
ConfigService $configService
) {
@@ -119,6 +124,7 @@ public function __construct(
$this->circleService = $circleService;
$this->memberService = $memberService;
$this->membershipService = $membershipService;
+ $this->permissionService = $permissionService;
$this->searchService = $searchService;
$this->configService = $configService;
@@ -139,6 +145,7 @@ public function __construct(
public function create(string $name, bool $personal = false, bool $local = false): DataResponse {
try {
$this->setCurrentFederatedUser();
+ $this->permissionService->confirmCircleCreation();
$circle = $this->circleService->create($name, null, $personal, $local);
@@ -572,14 +579,15 @@ public function link(string $circleId, string $singleId): DataResponse {
/**
+ * @return void
+ * @throws FederatedUserException
* @throws FederatedUserNotFoundException
+ * @throws FrontendException
* @throws InvalidIdException
- * @throws FederatedUserException
- * @throws SingleCircleNotFoundException
* @throws RequestBuilderException
- * @throws FrontendException
+ * @throws SingleCircleNotFoundException
*/
- private function setCurrentFederatedUser() {
+ private function setCurrentFederatedUser(): void {
if (!$this->configService->getAppValueBool(ConfigService::FRONTEND_ENABLED)) {
throw new FrontendException('frontend disabled');
}
diff --git a/lib/Exceptions/InsufficientPermissionException.php b/lib/Exceptions/InsufficientPermissionException.php
new file mode 100644
index 000000000..b3c075375
--- /dev/null
+++ b/lib/Exceptions/InsufficientPermissionException.php
@@ -0,0 +1,35 @@
+
+ * @copyright 2021
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+namespace OCA\Circles\Exceptions;
+
+class InsufficientPermissionException extends FederatedItemForbiddenException {
+}
diff --git a/lib/FederatedItems/CircleConfig.php b/lib/FederatedItems/CircleConfig.php
index 522c08477..293398450 100644
--- a/lib/FederatedItems/CircleConfig.php
+++ b/lib/FederatedItems/CircleConfig.php
@@ -34,12 +34,14 @@
use OCA\Circles\Db\CircleRequest;
use OCA\Circles\Exceptions\FederatedItemBadRequestException;
use OCA\Circles\Exceptions\FederatedItemException;
+use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\IFederatedItem;
use OCA\Circles\IFederatedItemAsyncProcess;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Federated\FederatedEvent;
use OCA\Circles\Model\Helpers\MemberHelper;
use OCA\Circles\Service\ConfigService;
+use OCA\Circles\Service\PermissionService;
use OCA\Circles\Tools\Traits\TDeserialize;
/**
@@ -56,6 +58,9 @@ class CircleConfig implements
/** @var CircleRequest */
private $circleRequest;
+ /** @var PermissionService */
+ private $permissionService;
+
/** @var ConfigService */
private $configService;
@@ -64,10 +69,16 @@ class CircleConfig implements
* CircleConfig constructor.
*
* @param CircleRequest $circleRequest
+ * @param PermissionService $permissionService
* @param ConfigService $configService
*/
- public function __construct(CircleRequest $circleRequest, ConfigService $configService) {
+ public function __construct(
+ CircleRequest $circleRequest,
+ PermissionService $permissionService,
+ ConfigService $configService
+ ) {
$this->circleRequest = $circleRequest;
+ $this->permissionService = $permissionService;
$this->configService = $configService;
}
@@ -76,6 +87,7 @@ public function __construct(CircleRequest $circleRequest, ConfigService $configS
* @param FederatedEvent $event
*
* @throws FederatedItemException
+ * @throws RequestBuilderException
*/
public function verify(FederatedEvent $event): void {
$circle = $event->getCircle();
@@ -150,7 +162,7 @@ public function verify(FederatedEvent $event): void {
$new = clone $circle;
$new->setConfig($config);
- $this->configService->confirmAllowedCircleTypes($new);
+ $this->permissionService->confirmAllowedCircleTypes($new, $circle);
$event->getData()->sInt('config', $new->getConfig());
diff --git a/lib/Model/Member.php b/lib/Model/Member.php
index 35ce1b212..4dd420b83 100644
--- a/lib/Model/Member.php
+++ b/lib/Model/Member.php
@@ -738,8 +738,10 @@ public function getMemberships(): array {
* @throws RequestBuilderException
*/
public function getLink(string $singleId, bool $detailed = false): Membership {
- $this->getManager()->getLink($this, $singleId, $detailed);
-
+ if ($singleId !== '') {
+ $this->getManager()->getLink($this, $singleId, $detailed);
+ }
+
throw new MembershipNotFoundException();
}
diff --git a/lib/Service/CircleService.php b/lib/Service/CircleService.php
index bf33bc474..b5f2d967a 100644
--- a/lib/Service/CircleService.php
+++ b/lib/Service/CircleService.php
@@ -101,6 +101,9 @@ class CircleService {
/** @var MemberService */
private $memberService;
+ /** @var PermissionService */
+ private $permissionService;
+
/** @var ConfigService */
private $configService;
@@ -114,6 +117,7 @@ class CircleService {
* @param FederatedUserService $federatedUserService
* @param FederatedEventService $federatedEventService
* @param MemberService $memberService
+ * @param PermissionService $permissionService
* @param ConfigService $configService
*/
public function __construct(
@@ -125,6 +129,7 @@ public function __construct(
FederatedUserService $federatedUserService,
FederatedEventService $federatedEventService,
MemberService $memberService,
+ PermissionService $permissionService,
ConfigService $configService
) {
$this->l10n = $l10n;
@@ -135,6 +140,7 @@ public function __construct(
$this->federatedUserService = $federatedUserService;
$this->federatedEventService = $federatedEventService;
$this->memberService = $memberService;
+ $this->permissionService = $permissionService;
$this->configService = $configService;
$this->setup('app', Application::APP_ID);
@@ -197,7 +203,7 @@ public function create(
}
$this->confirmName($circle);
- $this->configService->confirmAllowedCircleTypes($circle);
+ $this->permissionService->confirmAllowedCircleTypes($circle);
$member = new Member();
$member->importFromIFederatedUser($owner);
diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php
index 52f2aca31..e3604247a 100644
--- a/lib/Service/ConfigService.php
+++ b/lib/Service/ConfigService.php
@@ -106,6 +106,10 @@ class ConfigService {
public const ALLOWED_TYPES = 'allowed_types';
public const CIRCLE_TYPES_FORCE = 'circle_types_force';
public const CIRCLE_TYPES_BLOCK = 'circle_types_block';
+
+ public const BYPASS_CIRCLE_TYPES = 'bypass_circle_types';
+ public const LIMIT_CIRCLE_CREATION = 'limit_circle_creation';
+
public const MIGRATION_BYPASS = 'migration_bypass';
public const MIGRATION_22 = 'migration_22';
public const MIGRATION_22_1 = 'migration_22_1';
@@ -183,6 +187,9 @@ class ConfigService {
self::CIRCLE_TYPES_FORCE => '0',
self::CIRCLE_TYPES_BLOCK => '0',
+ self::BYPASS_CIRCLE_TYPES => '',
+ self::LIMIT_CIRCLE_CREATION => '',
+
self::MIGRATION_BYPASS => '0',
self::MIGRATION_22 => '0',
self::MIGRATION_22_1 => '0',
diff --git a/lib/Service/PermissionService.php b/lib/Service/PermissionService.php
new file mode 100644
index 000000000..81c8fbb94
--- /dev/null
+++ b/lib/Service/PermissionService.php
@@ -0,0 +1,184 @@
+
+ * @copyright 2022
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+
+namespace OCA\Circles\Service;
+
+use OCA\Circles\Exceptions\InitiatorNotFoundException;
+use OCA\Circles\Exceptions\InsufficientPermissionException;
+use OCA\Circles\Exceptions\MembershipNotFoundException;
+use OCA\Circles\Exceptions\RequestBuilderException;
+use OCA\Circles\Model\Circle;
+use OCP\IL10N;
+
+class PermissionService {
+
+
+ /** @var IL10N */
+ private $l10n;
+
+ /** @var FederatedUserService */
+ private $federatedUserService;
+
+ /** @var ConfigService */
+ private $configService;
+
+
+ /**
+ * @param IL10N $l10n
+ * @param FederatedUserService $federatedUserService
+ * @param ConfigService $configService
+ */
+ public function __construct(
+ IL10N $l10n,
+ FederatedUserService $federatedUserService,
+ ConfigService $configService
+ ) {
+ $this->l10n = $l10n;
+ $this->federatedUserService = $federatedUserService;
+ $this->configService = $configService;
+ }
+
+
+ /**
+ * @throws RequestBuilderException
+ * @throws InitiatorNotFoundException
+ * @throws InsufficientPermissionException
+ */
+ public function confirmCircleCreation(): void {
+ try {
+ $this->confirm(ConfigService::LIMIT_CIRCLE_CREATION);
+ } catch (InsufficientPermissionException $e) {
+ throw new InsufficientPermissionException(
+ $this->l10n->t('You have no permission to create a new circle')
+ );
+ }
+ }
+
+
+ /**
+ * @param string $config
+ *
+ * @throws InsufficientPermissionException
+ * @throws RequestBuilderException
+ * @throws InitiatorNotFoundException
+ */
+ private function confirm(string $config): void {
+ $singleId = $this->configService->getAppValue($config);
+ if ($singleId === '') {
+ return;
+ }
+
+ $this->federatedUserService->mustHaveCurrentUser();
+ $federatedUser = $this->federatedUserService->getCurrentUser();
+ try {
+ $federatedUser->getLink($singleId);
+ } catch (MembershipNotFoundException $e) {
+ throw new InsufficientPermissionException();
+ }
+ }
+
+
+ /**
+ * @param Circle $circle
+ *
+ * @return bool
+ * @throws RequestBuilderException
+ */
+ private function canBypassCircleTypes(Circle $circle): bool {
+ try {
+ if (!$circle->hasInitiator()) {
+ throw new MembershipNotFoundException();
+ }
+
+ $circle->getInitiator()->getLink(
+ $this->configService->getAppValue(ConfigService::BYPASS_CIRCLE_TYPES)
+ );
+
+ return true;
+ } catch (MembershipNotFoundException $e) {
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Enforce or Block circle's config/type
+ *
+ * @param Circle $circle
+ * @param Circle|null $previous
+ *
+ * @throws RequestBuilderException
+ */
+ public function confirmAllowedCircleTypes(Circle $circle, ?Circle $previous = null): void {
+ if ($this->canBypassCircleTypes($circle)) {
+ return;
+ }
+
+ $config = $circle->getConfig();
+ $force = $this->configService->getAppValueInt(ConfigService::CIRCLE_TYPES_FORCE);
+ $block = $this->configService->getAppValueInt(ConfigService::CIRCLE_TYPES_BLOCK);
+
+ if (is_null($previous)) {
+ $config |= $force;
+ $config &= ~$block;
+ } else {
+ // if we have a previous entry, we compare old and new config.
+ foreach (array_merge($this->extractBitwise($force), $this->extractBitwise($block)) as $bit) {
+ if ($previous->isConfig($bit)) {
+ $config |= $bit;
+ } else {
+ $config &= ~$bit;
+ }
+ }
+ }
+
+ $circle->setConfig($config);
+ }
+
+
+ /**
+ * @return int[]
+ */
+ private function extractBitwise(int $bitwise): array {
+ $values = [];
+ $b = 1;
+ while ($b <= $bitwise) {
+ if (($bitwise & $b) !== 0) {
+ $values[] = $b;
+ }
+
+ $b = $b << 1;
+ }
+
+ return $values;
+ }
+}
diff --git a/tests/unit/lib/Controller/LocalControllerTest.php b/tests/unit/lib/Controller/LocalControllerTest.php
index 7fe43e178..d7f461273 100644
--- a/tests/unit/lib/Controller/LocalControllerTest.php
+++ b/tests/unit/lib/Controller/LocalControllerTest.php
@@ -33,6 +33,7 @@
use OCA\Circles\Service\FederatedUserService;
use OCA\Circles\Service\MemberService;
use OCA\Circles\Service\MembershipService;
+use OCA\Circles\Service\PermissionService;
use OCA\Circles\Service\SearchService;
use OCA\Circles\Tools\Traits\TDeserialize;
use OCP\AppFramework\Http\DataResponse;
@@ -69,6 +70,9 @@ class LocalControllerTest extends TestCase {
/** @var SearchService|MockObject */
private $searchService;
+ /** @var PermissionService|MockObject */
+ private $permissionService;
+
/** @var ConfigService|MockObject */
private $configService;
@@ -84,9 +88,19 @@ public function setUp(): void {
$this->memberService = $this->createMock(MemberService::class);
$this->membershipService = $this->createMock(MembershipService::class);
$this->searchService = $this->createMock(SearchService::class);
+ $this->permissionService = $this->createMock(PermissionService::class);
$this->configService = $this->createMock(ConfigService::class);
$this->configService->expects($this->any())->method('getAppValueBool')->with(ConfigService::FRONTEND_ENABLED)->willReturn(true);
- $this->localController = new LocalController(Application::APP_ID, $this->request, $this->userSession, $this->federatedUserService, $this->circleService, $this->memberService, $this->membershipService, $this->searchService, $this->configService);
+ $this->localController = new LocalController(Application::APP_ID,
+ $this->request,
+ $this->userSession,
+ $this->federatedUserService,
+ $this->circleService,
+ $this->memberService,
+ $this->membershipService,
+ $this->permissionService,
+ $this->searchService,
+ $this->configService);
}
/**