Skip to content

Commit

Permalink
feat: dynamically evaluate page access permissions
Browse files Browse the repository at this point in the history
Instead of loading all pages as separate resources into the ACL, we dynamically
evaluate whether someone may access the page or not.

A visitor can access a page if they have the required role or if they inherit
the required role.
  • Loading branch information
tomudding committed Dec 29, 2024
1 parent 43c1cfb commit 5d63c8b
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 67 deletions.
2 changes: 1 addition & 1 deletion module/Frontpage/src/Model/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,6 @@ public function toArray(): array
*/
public function getResourceId(): string
{
return 'page' . $this->getId();
return 'page';
}
}
26 changes: 1 addition & 25 deletions module/Frontpage/src/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,8 @@
use Frontpage\Service\Page as PageService;
use Frontpage\Service\Poll as PollService;
use Psr\Container\ContainerInterface;
use RuntimeException;
use User\Authorization\AclServiceFactory;

use function sprintf;

class Module
{
/**
Expand All @@ -58,28 +55,7 @@ public function getServiceConfig(): array
return [
'factories' => [
// Services
AclService::class => static function (
ContainerInterface $container,
$requestedName,
?array $options = null,
) {
$aclService = (new AclServiceFactory())->__invoke($container, $requestedName, $options);

if ($aclService instanceof AclService) {
$pages = $container->get(PageMapper::class)->findAll();
$aclService->setPages($pages);

return $aclService;
}

throw new RuntimeException(
sprintf(
'Expected service of type %s, got service of type %s',
AclService::class,
$aclService::class,
),
);
},
AclService::class => AclServiceFactory::class,
FrontpageService::class => FrontpageServiceFactory::class,
NewsService::class => NewsServiceFactory::class,
PageService::class => PageServiceFactory::class,
Expand Down
16 changes: 3 additions & 13 deletions module/Frontpage/src/Service/AclService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,11 @@

namespace Frontpage\Service;

use Frontpage\Model\Page as PageModel;
use Laminas\Permissions\Acl\Resource\GenericResource as Resource;
use User\Permissions\Assertion\IsAllowedToViewPage;

class AclService extends \User\Service\AclService
{
/**
* @param PageModel[] $pages
*/
public function setPages(array $pages): void
{
foreach ($pages as $page) {
$requiredRole = $page->getRequiredRole()->value;
$this->acl->addResource($page);
$this->acl->allow($requiredRole, $page, 'view');
}
}

protected function createAcl(): void
{
parent::createAcl();
Expand All @@ -36,5 +24,7 @@ protected function createAcl(): void
$this->acl->allow('user', 'infimum', 'view');
$this->acl->allow('user', 'poll', ['vote', 'request']);
$this->acl->allow('user', 'poll_comment', ['view', 'create', 'list']);

$this->acl->allow(null, 'page', 'view', new IsAllowedToViewPage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,7 @@
class IsAfterMembershipEndedAndNotTagged implements AssertionInterface
{
/**
* Returns true if and only if the assertion conditions are met.
*
* This method is passed the ACL, Role, Resource, and privilege to which the authorization query applies. If the
* $role, $resource, or $privilege parameters are null, it means that the query applies to all Roles, Resources, or
* privileges, respectively.
*
* @param string|null $privilege
* @inheritDoc
*/
public function assert(
Acl $acl,
Expand Down
35 changes: 35 additions & 0 deletions module/User/src/Permissions/Assertion/IsAllowedToViewPage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace User\Permissions\Assertion;

use Frontpage\Model\Page as PageModel;
use Laminas\Permissions\Acl\Acl;
use Laminas\Permissions\Acl\Assertion\AssertionInterface;
use Laminas\Permissions\Acl\Resource\ResourceInterface;
use Laminas\Permissions\Acl\Role\RoleInterface;

/**
* Assertion to check if whoever is trying to view the page is allowed to view the page.
*/
class IsAllowedToViewPage implements AssertionInterface
{
/**
* @inheritDoc
*/
public function assert(
Acl $acl,
?RoleInterface $role = null,
?ResourceInterface $resource = null,
$privilege = null,
): bool {
if (!$resource instanceof PageModel) {
return false;
}

$requiredRole = $resource->getRequiredRole()->value;

return $role->getRoleId() === $requiredRole || $acl->inheritsRole($role, $requiredRole);
}
}
8 changes: 1 addition & 7 deletions module/User/src/Permissions/Assertion/IsCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@
class IsCreator implements AssertionInterface
{
/**
* Returns true if and only if the assertion conditions are met.
*
* This method is passed the ACL, Role, Resource, and privilege to which the authorization query applies. If the
* $role, $resource, or $privilege parameters are null, it means that the query applies to all Roles, Resources, or
* privileges, respectively.
*
* @param string|null $privilege
* @inheritDoc
*/
public function assert(
Acl $acl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@
class IsCreatorOrOrganMember implements AssertionInterface
{
/**
* Returns true if and only if the assertion conditions are met.
*
* This method is passed the ACL, Role, Resource, and privilege to which the authorization query applies. If the
* $role, $resource, or $privilege parameters are null, it means that the query applies to all Roles, Resources, or
* privileges, respectively.
*
* @param string|null $privilege
* @inheritDoc
*/
public function assert(
Acl $acl,
Expand Down
8 changes: 1 addition & 7 deletions module/User/src/Permissions/Assertion/IsOrganMember.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@
class IsOrganMember implements AssertionInterface
{
/**
* Returns true if and only if the assertion conditions are met.
*
* This method is passed the ACL, Role, Resource, and privilege to which the authorization query applies. If the
* $role, $resource, or $privilege parameters are null, it means that the query applies to all Roles, Resources, or
* privileges, respectively.
*
* @param string|null $privilege
* @inheritDoc
*/
public function assert(
Acl $acl,
Expand Down
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
<exclude-pattern>module/User/src/Service/Factory/UserFactory.php</exclude-pattern>
<!-- Permission assertions -->
<exclude-pattern>module/User/src/Permissions/Assertion/IsAfterMembershipEndedAndNotTagged.php</exclude-pattern>
<exclude-pattern>module/User/src/Permissions/Assertion/IsAllowedToViewPage.php</exclude-pattern>
<exclude-pattern>module/User/src/Permissions/Assertion/IsCreator.php</exclude-pattern>
<exclude-pattern>module/User/src/Permissions/Assertion/IsCreatorOrOrganMember.php</exclude-pattern>
<exclude-pattern>module/User/src/Permissions/Assertion/IsOrganMember.php</exclude-pattern>
Expand Down

0 comments on commit 5d63c8b

Please sign in to comment.