bypass/limit permissions

Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Co-authored-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Maxence Lange 2022-04-04 11:11:47 -01:00
parent 87725112af
commit d6544d41e3
9 changed files with 289 additions and 13 deletions

View file

@ -172,6 +172,14 @@ class CirclesConfig extends Base {
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;

View file

@ -31,8 +31,6 @@ declare(strict_types=1);
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\ConfigService;
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 @@ class LocalController extends OcsController {
CircleService $circleService,
MemberService $memberService,
MembershipService $membershipService,
PermissionService $permissionService,
SearchService $searchService,
ConfigService $configService
) {
@ -119,6 +124,7 @@ class LocalController extends OcsController {
$this->circleService = $circleService;
$this->memberService = $memberService;
$this->membershipService = $membershipService;
$this->permissionService = $permissionService;
$this->searchService = $searchService;
$this->configService = $configService;
@ -139,6 +145,7 @@ class LocalController extends OcsController {
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 @@ class LocalController extends OcsController {
/**
* @throws FederatedUserNotFoundException
* @throws InvalidIdException
* @return void
* @throws FederatedUserException
* @throws SingleCircleNotFoundException
* @throws RequestBuilderException
* @throws FederatedUserNotFoundException
* @throws FrontendException
* @throws InvalidIdException
* @throws RequestBuilderException
* @throws SingleCircleNotFoundException
*/
private function setCurrentFederatedUser() {
private function setCurrentFederatedUser(): void {
if (!$this->configService->getAppValueBool(ConfigService::FRONTEND_ENABLED)) {
throw new FrontendException('frontend disabled');
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @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 <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Circles\Exceptions;
class InsufficientPermissionException extends FederatedItemForbiddenException {
}

View file

@ -34,12 +34,14 @@ namespace OCA\Circles\FederatedItems;
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 @@ class CircleConfig implements
* @param FederatedEvent $event
*
* @throws FederatedItemException
* @throws RequestBuilderException
*/
public function verify(FederatedEvent $event): void {
$circle = $event->getCircle();
@ -150,7 +162,7 @@ class CircleConfig implements
$new = clone $circle;
$new->setConfig($config);
$this->configService->confirmAllowedCircleTypes($new);
$this->permissionService->confirmAllowedCircleTypes($new, $circle);
$event->getData()->sInt('config', $new->getConfig());

View file

@ -738,8 +738,10 @@ class Member extends ManagedModel implements
* @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();
}

View file

@ -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 @@ class CircleService {
FederatedUserService $federatedUserService,
FederatedEventService $federatedEventService,
MemberService $memberService,
PermissionService $permissionService,
ConfigService $configService
) {
$this->l10n = $l10n;
@ -135,6 +140,7 @@ class CircleService {
$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 @@ class CircleService {
}
$this->confirmName($circle);
$this->configService->confirmAllowedCircleTypes($circle);
$this->permissionService->confirmAllowedCircleTypes($circle);
$member = new Member();
$member->importFromIFederatedUser($owner);

View file

@ -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',

View file

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
/**
* Circles - Bring cloud-users closer together.
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*
* @author Maxence Lange <maxence@artificial-owl.com>
* @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 <http://www.gnu.org/licenses/>.
*
*/
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;
}
}

View file

@ -33,6 +33,7 @@ use OCA\Circles\Service\ConfigService;
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 @@ class LocalControllerTest extends TestCase {
$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);
}
/**