Skip to content

Commit ed1ba7a

Browse files
committed
refactor: optimize permission wildcard matching
Signed-off-by: memleakd <121398829+memleakd@users.noreply.github.com>
1 parent b0150be commit ed1ba7a

1 file changed

Lines changed: 65 additions & 27 deletions

File tree

‎src/Authorization/PermissionMatcher.php‎

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,75 +23,113 @@ final class PermissionMatcher
2323
*/
2424
public static function matches(string $permission, array $grants): bool
2525
{
26-
if (! self::isValid($permission)) {
26+
$permissionSegments = self::splitIfValid($permission);
27+
28+
if ($permissionSegments === null) {
2729
return false;
2830
}
2931

32+
$permissionSegmentCount = count($permissionSegments);
33+
3034
foreach ($grants as $grant) {
31-
if (! self::isValid($grant)) {
35+
if ($grant === $permission) {
36+
return true;
37+
}
38+
39+
$wildcardPosition = strpos($grant, '*');
40+
41+
if ($wildcardPosition === false) {
3242
continue;
3343
}
3444

35-
if ($grant === $permission) {
36-
return true;
45+
if (
46+
$wildcardPosition > 0
47+
&& $wildcardPosition === strlen($grant) - 1
48+
&& $grant[$wildcardPosition - 1] === '.'
49+
) {
50+
$prefix = substr($grant, 0, $wildcardPosition - 1);
51+
52+
if (self::isLiteral($prefix) && str_starts_with($permission, $prefix . '.')) {
53+
return true;
54+
}
55+
56+
continue;
3757
}
3858

39-
if (str_contains($grant, '*') && self::matchesWildcardGrant($grant, $permission)) {
59+
$grantSegments = self::splitIfValid($grant);
60+
61+
if ($grantSegments === null) {
62+
continue;
63+
}
64+
65+
if (self::matchesWildcardGrant($grantSegments, $permissionSegments, $permissionSegmentCount)) {
4066
return true;
4167
}
4268
}
4369

4470
return false;
4571
}
4672

47-
private static function matchesWildcardGrant(string $grant, string $permission): bool
73+
/**
74+
* @param list<string> $grantSegments
75+
* @param list<string> $permissionSegments
76+
*/
77+
private static function matchesWildcardGrant(array $grantSegments, array $permissionSegments, int $permissionSegmentCount): bool
4878
{
49-
$grantSegments = explode('.', $grant);
50-
$permissionSegments = explode('.', $permission);
79+
$grantSegmentCount = count($grantSegments);
5180

52-
if (end($grantSegments) === '*') {
53-
array_pop($grantSegments);
81+
if ($grantSegments[$grantSegmentCount - 1] === '*') {
82+
$grantSegmentCount--;
5483

55-
return count($permissionSegments) > count($grantSegments)
56-
&& self::segmentsMatch($grantSegments, array_slice($permissionSegments, 0, count($grantSegments)));
84+
return $permissionSegmentCount > $grantSegmentCount
85+
&& self::segmentsMatch($grantSegments, $permissionSegments, $grantSegmentCount);
5786
}
5887

59-
return self::segmentsMatch($grantSegments, $permissionSegments);
88+
return $permissionSegmentCount === $grantSegmentCount
89+
&& self::segmentsMatch($grantSegments, $permissionSegments, $grantSegmentCount);
6090
}
6191

6292
/**
6393
* @param list<string> $grantSegments
6494
* @param list<string> $permissionSegments
6595
*/
66-
private static function segmentsMatch(array $grantSegments, array $permissionSegments): bool
96+
private static function segmentsMatch(array $grantSegments, array $permissionSegments, int $segmentCount): bool
6797
{
68-
if (count($grantSegments) !== count($permissionSegments)) {
69-
return false;
70-
}
71-
72-
foreach ($grantSegments as $index => $grantSegment) {
73-
if ($grantSegment !== '*' && $grantSegment !== $permissionSegments[$index]) {
98+
for ($index = 0; $index < $segmentCount; $index++) {
99+
if ($grantSegments[$index] !== '*' && $grantSegments[$index] !== $permissionSegments[$index]) {
74100
return false;
75101
}
76102
}
77103

78104
return true;
79105
}
80106

81-
private static function isValid(string $permission): bool
107+
/**
108+
* @return list<string>|null
109+
*/
110+
private static function splitIfValid(string $permission): ?array
82111
{
83-
$segments = explode('.', $permission);
84-
85-
if ($segments === ['*'] || $segments[0] === '*') {
86-
return false;
112+
if ($permission === '' || str_starts_with($permission, '*')) {
113+
return null;
87114
}
88115

116+
$segments = explode('.', $permission);
117+
89118
foreach ($segments as $segment) {
90119
if ($segment === '' || ($segment !== '*' && str_contains($segment, '*'))) {
91-
return false;
120+
return null;
92121
}
93122
}
94123

95-
return true;
124+
return $segments;
125+
}
126+
127+
private static function isLiteral(string $permission): bool
128+
{
129+
return $permission !== ''
130+
&& ! str_contains($permission, '*')
131+
&& ! str_contains($permission, '..')
132+
&& ! str_starts_with($permission, '.')
133+
&& ! str_ends_with($permission, '.');
96134
}
97135
}

0 commit comments

Comments
 (0)