Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions src/Authorization/PermissionMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

/**
* This file is part of Blitz PHP framework - Schild.
*
* (c) 2023 Dimitri Sitchet Tomkeu <devcode.dst@gmail.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace BlitzPHP\Schild\Authorization;

/**
* Compare les autorisations accordées aux noms des autorisations demandées pour les mécanismes internes d'autorisation de Schild.
*/
final class PermissionMatcher
{
/**
* @param list<string> $grants
*/
public static function matches(string $permission, array $grants): bool
{
$permissionSegments = self::splitIfValid($permission);

if ($permissionSegments === null) {
return false;
}

$permissionSegmentCount = count($permissionSegments);

foreach ($grants as $grant) {
if ($grant === $permission) {
return true;
}

$wildcardPosition = strpos($grant, '*');

if ($wildcardPosition === false) {
continue;
}

if (
$wildcardPosition > 0
&& $wildcardPosition === strlen($grant) - 1
&& $grant[$wildcardPosition - 1] === '.'
) {
$prefix = substr($grant, 0, $wildcardPosition - 1);

if (self::isLiteral($prefix) && str_starts_with($permission, $prefix . '.')) {
return true;
}

continue;
}

$grantSegments = self::splitIfValid($grant);

if ($grantSegments === null) {
continue;
}

if (self::matchesWildcardGrant($grantSegments, $permissionSegments, $permissionSegmentCount)) {
return true;
}
}

return false;
}

/**
* @param list<string> $grantSegments
* @param list<string> $permissionSegments
*/
private static function matchesWildcardGrant(array $grantSegments, array $permissionSegments, int $permissionSegmentCount): bool
{
$grantSegmentCount = count($grantSegments);

if ($grantSegments[$grantSegmentCount - 1] === '*') {
$grantSegmentCount--;

return $permissionSegmentCount > $grantSegmentCount
&& self::segmentsMatch($grantSegments, $permissionSegments, $grantSegmentCount);
}

return $permissionSegmentCount === $grantSegmentCount
&& self::segmentsMatch($grantSegments, $permissionSegments, $grantSegmentCount);
}

/**
* @param list<string> $grantSegments
* @param list<string> $permissionSegments
*/
private static function segmentsMatch(array $grantSegments, array $permissionSegments, int $segmentCount): bool
{
for ($index = 0; $index < $segmentCount; $index++) {
if ($grantSegments[$index] !== '*' && $grantSegments[$index] !== $permissionSegments[$index]) {
return false;
}
}

return true;
}

/**
* @return list<string>|null
*/
private static function splitIfValid(string $permission): ?array
{
if ($permission === '' || str_starts_with($permission, '*')) {
return null;
}

$segments = explode('.', $permission);

foreach ($segments as $segment) {
if ($segment === '' || ($segment !== '*' && str_contains($segment, '*'))) {
return null;
}
}

return $segments;
}

private static function isLiteral(string $permission): bool
{
return $permission !== ''
&& ! str_contains($permission, '*')
&& ! str_contains($permission, '..')
&& ! str_starts_with($permission, '.')
&& ! str_ends_with($permission, '.');
}
}
22 changes: 4 additions & 18 deletions src/Authorization/Traits/Authorizable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace BlitzPHP\Schild\Authorization\Traits;

use BlitzPHP\Schild\Authorization\PermissionMatcher;
use BlitzPHP\Schild\Exceptions\AuthorizationException;
use BlitzPHP\Schild\Exceptions\LogicException;
use BlitzPHP\Schild\Models\GroupModel;
Expand Down Expand Up @@ -239,7 +240,7 @@ public function hasPermission(string $permission): bool
* Vérifie les autorisations d'utilisateur et leurs autorisations de groupe
* pour voir si l'utilisateur dispose d'une autorisation spécifique.
*
* @param string $permission chaînes composées d'une portée et d'une action, comme les users.create
* @param string $permissions Chaîne(s) d'autorisation, généralement séparées par des points, comme `users.create`
*/
public function can(string ...$permissions): bool
{
Expand All @@ -253,18 +254,10 @@ public function can(string ...$permissions): bool
$matrix = parametre('auth-groups.matrix');

foreach ($permissions as $permission) {
// L'autorisation doit contenir une portée et une action
if (! str_contains($permission, '.')) {
throw new LogicException(
'Une autorisation doit être une chaîne composée d\'une portée et d\'une action, comme `users.create`.'
. ' Autorisation non valide: ' . $permission,
);
}

$permission = strtolower($permission);

// Vérifier les autorisations de l'utilisateur
if (in_array($permission, $this->permissionsCache, true)) {
if (PermissionMatcher::matches($permission, $this->permissionsCache)) {
return true;
}

Expand All @@ -273,14 +266,7 @@ public function can(string ...$permissions): bool
}

foreach ($this->groupCache as $group) {
// Vérifier correspondance exacte
if (isset($matrix[$group]) && in_array($permission, $matrix[$group], true)) {
return true;
}

// Vérifier match joker
$check = substr($permission, 0, strpos($permission, '.')) . '.*';
if (isset($matrix[$group]) && in_array($check, $matrix[$group], true)) {
if (isset($matrix[$group]) && PermissionMatcher::matches($permission, $matrix[$group])) {
return true;
}
}
Expand Down
14 changes: 5 additions & 9 deletions src/Entities/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace BlitzPHP\Schild\Entities;

use BlitzPHP\Schild\Authorization\PermissionMatcher;

/**
* Représente un seul groupe d'utilisateurs et fournit des fonctions utilitaires.
*/
Expand Down Expand Up @@ -75,15 +77,9 @@ public function can(string $permission): bool
{
$this->populatePermissions();

// Vérifier la correspondance exacte
if (! empty($this->permissions) && in_array($permission, $this->permissions, true)) {
return true;
}

// Vérifier la correspondance générique
$check = substr($permission, 0, strpos($permission, '.')) . '.*';

return ! empty($this->permissions) && in_array($check, $this->permissions, true);
return $this->permissions !== null
&& $this->permissions !== []
&& PermissionMatcher::matches($permission, $this->permissions);
}

/**
Expand Down