Skip to content

Commit 12f9e0e

Browse files
committed
Optimizing ACL policies with caching of group-membership check results.
1 parent 4155f3e commit 12f9e0e

4 files changed

Lines changed: 90 additions & 37 deletions

File tree

app/V1Module/security/Policies/AssignmentSolutionPermissionPolicy.php

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
namespace App\Security\Policies;
44

55
use App\Model\Entity\AssignmentSolution;
6-
use App\Model\Entity\AssignmentSolutionSubmission;
6+
use App\Model\Entity\GroupMembership;
77
use App\Security\Identity;
88

9-
class AssignmentSolutionPermissionPolicy implements IPermissionPolicy
9+
class AssignmentSolutionPermissionPolicy extends BasePermissionPolicy implements IPermissionPolicy
1010
{
1111
public function getAssociatedClass()
1212
{
@@ -20,14 +20,11 @@ public function isAdmin(Identity $identity, AssignmentSolution $solution)
2020
return false;
2121
}
2222

23-
$group = $assignment->getGroup();
24-
$user = $identity->getUserData();
25-
26-
if ($user === null) {
27-
return false;
28-
}
29-
30-
return $group && $group->isAdminOf($user);
23+
return $this->checkMinimalMembership(
24+
$identity->getUserData(),
25+
$assignment->getGroup(),
26+
GroupMembership::TYPE_ADMIN
27+
);
3128
}
3229

3330
public function isSupervisorOrAdmin(Identity $identity, AssignmentSolution $solution)
@@ -37,14 +34,11 @@ public function isSupervisorOrAdmin(Identity $identity, AssignmentSolution $solu
3734
return false;
3835
}
3936

40-
$group = $assignment->getGroup();
41-
$user = $identity->getUserData();
42-
43-
if ($user === null) {
44-
return false;
45-
}
46-
47-
return $group && ($group->isSupervisorOf($user) || $group->isAdminOf($user));
37+
return $this->checkMinimalMembership(
38+
$identity->getUserData(),
39+
$assignment->getGroup(),
40+
GroupMembership::TYPE_SUPERVISOR
41+
);
4842
}
4943

5044
public function isObserverOrBetter(Identity $identity, AssignmentSolution $solution)
@@ -54,14 +48,11 @@ public function isObserverOrBetter(Identity $identity, AssignmentSolution $solut
5448
return false;
5549
}
5650

57-
$group = $assignment->getGroup();
58-
$user = $identity->getUserData();
59-
60-
if ($user === null) {
61-
return false;
62-
}
63-
64-
return $group && ($group->isObserverOf($user) || $group->isSupervisorOf($user) || $group->isAdminOf($user));
51+
return $this->checkMinimalMembership(
52+
$identity->getUserData(),
53+
$assignment->getGroup(),
54+
GroupMembership::TYPE_OBSERVER
55+
);
6556
}
6657

6758
public function isAuthor(Identity $identity, AssignmentSolution $solution)

app/V1Module/security/Policies/BasePermissionPolicy.php

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,64 @@
22

33
namespace App\Security\Policies;
44

5-
use App\Model\Entity\Instance;
5+
use App\Model\Entity\GroupMembership;
6+
use App\Model\Entity\Group;
67
use App\Model\Entity\User;
7-
use App\Security\Identity;
88
use App\Security\Roles;
9+
use InvalidArgumentException;
910

1011
/**
11-
* Base policy is not bound to particular entity.
12-
* It gathers generic checks performed solely on the entity of the logged-in user.
12+
* Base policy class that implements caching mechanisms reusable by other policies.
1313
*/
14-
class BasePermissionPolicy implements IPermissionPolicy
14+
class BasePermissionPolicy
1515
{
16-
public function getAssociatedClass()
16+
private const MEMBERSHIPS = [
17+
GroupMembership::TYPE_STUDENT => 1,
18+
GroupMembership::TYPE_OBSERVER => 2,
19+
GroupMembership::TYPE_SUPERVISOR => 3,
20+
GroupMembership::TYPE_ADMIN => 4,
21+
];
22+
23+
private array $membershipCache = [];
24+
25+
protected function getMembershipLevel(User $user, Group $group): int
1726
{
18-
return '';
27+
$gid = $group->getId();
28+
if (!array_key_exists($gid, $this->membershipCache)) {
29+
$this->membershipCache[$gid] = 0; // Not a member
30+
31+
if ($user->getRole() === Roles::STUDENT_ROLE) {
32+
if ($group->isStudentOf($user)) {
33+
$this->membershipCache[$gid] = self::MEMBERSHIPS[GroupMembership::TYPE_STUDENT];
34+
}
35+
} else {
36+
if ($group->isAdminOf($user)) {
37+
$this->membershipCache[$gid] = self::MEMBERSHIPS[GroupMembership::TYPE_ADMIN];
38+
} elseif ($group->isSupervisorOf($user)) {
39+
$this->membershipCache[$gid] = self::MEMBERSHIPS[GroupMembership::TYPE_SUPERVISOR];
40+
} elseif ($group->isObserverOf($user)) {
41+
$this->membershipCache[$gid] = self::MEMBERSHIPS[GroupMembership::TYPE_OBSERVER];
42+
} elseif ($user->getRole() === Roles::STUDENT_ROLE && $group->isStudentOf($user)) {
43+
$this->membershipCache[$gid] = self::MEMBERSHIPS[GroupMembership::TYPE_STUDENT];
44+
}
45+
}
46+
}
47+
48+
return $this->membershipCache[$gid];
1949
}
2050

21-
public function userIsNotGroupLocked(Identity $identity): bool
51+
protected function checkMinimalMembership(?User $user, ?Group $group, string $membership): bool
2252
{
23-
$user = $identity->getUserData();
24-
return $user && !$user->isGroupLocked();
53+
if (!$user || !$group) {
54+
return false;
55+
}
56+
57+
$minLevel = self::MEMBERSHIPS[$membership] ?? null;
58+
if ($minLevel === null) {
59+
throw new InvalidArgumentException("Unknown membership type: $membership");
60+
}
61+
62+
$level = $this->getMembershipLevel($user, $group);
63+
return $level >= $minLevel;
2564
}
2665
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Security\Policies;
4+
5+
use App\Security\Identity;
6+
7+
/**
8+
* A policy that is not bound to a particular entity.
9+
* It gathers generic checks performed solely on the entity of the logged-in user.
10+
*/
11+
class GenericPermissionPolicy implements IPermissionPolicy
12+
{
13+
public function getAssociatedClass()
14+
{
15+
return '';
16+
}
17+
18+
public function userIsNotGroupLocked(Identity $identity): bool
19+
{
20+
$user = $identity->getUserData();
21+
return $user && !$user->isGroupLocked();
22+
}
23+
}

app/config/config.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ acl:
255255
asyncJob: App\Security\ACL\IAsyncJobPermissions
256256
plagiarism: App\Security\ACL\IPlagiarismPermissions
257257
policies:
258-
_: App\Security\Policies\BasePermissionPolicy
258+
_: App\Security\Policies\GenericPermissionPolicy
259259
group: App\Security\Policies\GroupPermissionPolicy
260260
instance: App\Security\Policies\InstancePermissionPolicy
261261
user: App\Security\Policies\UserPermissionPolicy

0 commit comments

Comments
 (0)