Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
258
app/Services/PermissionService.php
Normal file
258
app/Services/PermissionService.php
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class PermissionService
|
||||
{
|
||||
public const ROLES = [
|
||||
'admin' => 'Administrateur',
|
||||
'responsable' => 'Responsable',
|
||||
'benevole' => 'Bénévole',
|
||||
'lecture' => 'Lecture seule',
|
||||
];
|
||||
public const MODULES = [
|
||||
'dashboard' => ['Dashboard', 'Priorités et situation quotidienne'],
|
||||
'animals' => ['Animaux', 'Fiches, portées, liens, médias et adoptions'],
|
||||
'medical' => ['Médical', 'Notes, traitements, vaccinations et décès'],
|
||||
'care' => ['Tournées', 'Passages tablette et configuration des box'],
|
||||
'directory' => ['Annuaire', 'Personnes, structures et coordonnées'],
|
||||
'agenda' => ['Agenda', 'Rendez-vous, transports, visites et tâches partagées'],
|
||||
'statistics' => ['Statistiques', 'Analyses annuelles et tendances'],
|
||||
'administrative' => ['Administratif', 'Registre, documents et exports'],
|
||||
'accounting' => ['Comptabilité', 'Factures, avoirs et règlements'],
|
||||
'grants' => ['Subventions', 'Bilans, demandes et justificatifs'],
|
||||
];
|
||||
public static function roleLabel(string $key): string
|
||||
{
|
||||
return t('permission.role.' . $key, [], self::ROLES[$key] ?? $key);
|
||||
}
|
||||
public static function moduleLabel(string $key): string
|
||||
{
|
||||
return t('permission.module.' . $key, [], self::MODULES[$key][0] ?? $key);
|
||||
}
|
||||
public static function moduleDescription(string $key): string
|
||||
{
|
||||
return t('permission.module_help.' . $key, [], self::MODULES[$key][1] ?? '');
|
||||
}
|
||||
|
||||
private static array $cache = [];
|
||||
public static function can(string $module, string $action = 'view', ?string $role = null): bool
|
||||
{
|
||||
$role ??= (string) (Auth::user()['role'] ?? '');
|
||||
if ($role === 'admin') {
|
||||
return true;
|
||||
}
|
||||
if (!isset(self::MODULES[$module], self::ROLES[$role])) {
|
||||
return false;
|
||||
}
|
||||
if (!isset(self::$cache[$role])) {
|
||||
$s = DB::pdo()->prepare('SELECT module,can_view,can_edit FROM role_permissions WHERE role=?');
|
||||
$s->execute([$role]);
|
||||
self::$cache[$role] = [];
|
||||
foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
self::$cache[$role][$row['module']] = [
|
||||
'view' => (bool) $row['can_view'],
|
||||
'edit' => (bool) $row['can_edit'],
|
||||
];
|
||||
}
|
||||
}
|
||||
return (bool) (self::$cache[$role][$module][$action === 'edit' ? 'edit' : 'view'] ?? false);
|
||||
}
|
||||
public static function matrix(): array
|
||||
{
|
||||
$matrix = [];
|
||||
foreach (array_keys(self::ROLES) as $role) {
|
||||
foreach (array_keys(self::MODULES) as $module) {
|
||||
$matrix[$role][$module] = [
|
||||
'view' => self::can($module, 'view', $role),
|
||||
'edit' => self::can($module, 'edit', $role),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $matrix;
|
||||
}
|
||||
public static function moduleForPath(string $path): ?string
|
||||
{
|
||||
if ($path === '/') {
|
||||
return 'dashboard';
|
||||
}
|
||||
if (str_starts_with($path, '/animal/documents')) {
|
||||
return 'medical';
|
||||
}
|
||||
if (
|
||||
str_starts_with($path, '/animals') ||
|
||||
str_starts_with($path, '/animal') ||
|
||||
str_starts_with($path, '/litter') ||
|
||||
str_starts_with($path, '/bonded')
|
||||
) {
|
||||
foreach (
|
||||
[
|
||||
'/animal/add-medical',
|
||||
'/animal/medical-status',
|
||||
'/animal/add-treatment',
|
||||
'/animal/add-vaccine',
|
||||
'/animal/death',
|
||||
'/animal/prescription',
|
||||
'/animal/lab-report',
|
||||
'/animal/medical-document',
|
||||
'/animal/surgery',
|
||||
]
|
||||
as $medical
|
||||
) {
|
||||
if (str_starts_with($path, $medical)) {
|
||||
return 'medical';
|
||||
}
|
||||
}
|
||||
return 'animals';
|
||||
}
|
||||
if (str_starts_with($path, '/dashboard')) {
|
||||
return 'dashboard';
|
||||
}
|
||||
if (str_starts_with($path, '/care-round')) {
|
||||
return 'care';
|
||||
}
|
||||
if (str_starts_with($path, '/directory')) {
|
||||
return 'directory';
|
||||
}
|
||||
if (str_starts_with($path, '/agenda')) {
|
||||
return 'agenda';
|
||||
}
|
||||
if (str_starts_with($path, '/statistics')) {
|
||||
return 'statistics';
|
||||
}
|
||||
if (str_starts_with($path, '/accounting')) {
|
||||
return 'accounting';
|
||||
}
|
||||
if (str_starts_with($path, '/admin/grants')) {
|
||||
return 'grants';
|
||||
}
|
||||
if (str_starts_with($path, '/admin') || str_starts_with($path, '/documents')) {
|
||||
return 'administrative';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static function enforce(string $path, string $method): void
|
||||
{
|
||||
if (
|
||||
str_starts_with($path, '/settings') ||
|
||||
str_starts_with($path, '/dev/') ||
|
||||
in_array($path, ['/logout', '/evolutions'], true)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
$module = self::moduleForPath($path);
|
||||
if (!$module) {
|
||||
return;
|
||||
}
|
||||
$action = strtoupper($method) === 'POST' || self::isEditPage($path) ? 'edit' : 'view';
|
||||
$allowed = self::can($module, $action) && ($module !== 'medical' || self::can('animals', 'view'));
|
||||
if (!$allowed) {
|
||||
AuditService::log('authorization_denied', $path, 'Accès refusé au module ' . $module, null, null, [
|
||||
'module' => $module,
|
||||
'requested_action' => $action,
|
||||
]);
|
||||
http_response_code(403);
|
||||
if (function_exists('render')) {
|
||||
render('access_denied.php', [
|
||||
'title' => t('access.denied'),
|
||||
'deniedAction' => $action,
|
||||
'deniedModule' => self::moduleLabel($module),
|
||||
]);
|
||||
} else {
|
||||
echo t('access.denied');
|
||||
}
|
||||
exit();
|
||||
}
|
||||
}
|
||||
public static function firstAllowedPath(): string
|
||||
{
|
||||
foreach (
|
||||
[
|
||||
'dashboard' => '/dashboard',
|
||||
'animals' => '/animals',
|
||||
'care' => '/care-round',
|
||||
'directory' => '/directory',
|
||||
'agenda' => '/agenda',
|
||||
'statistics' => '/statistics',
|
||||
'accounting' => '/accounting',
|
||||
'administrative' => '/admin/register',
|
||||
'grants' => '/admin/grants',
|
||||
]
|
||||
as $module => $path
|
||||
) {
|
||||
if (self::can($module)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
return '/evolutions';
|
||||
}
|
||||
public static function canOpenPath(string $path): bool
|
||||
{
|
||||
$module = self::moduleForPath($path);
|
||||
return $module === null ||
|
||||
(self::can($module, self::isEditPage($path) ? 'edit' : 'view') &&
|
||||
($module !== 'medical' || self::can('animals')));
|
||||
}
|
||||
public static function currentPermissions(): array
|
||||
{
|
||||
$out = [];
|
||||
foreach (self::MODULES as $module => $unused) {
|
||||
$out[$module] = ['view' => self::can($module), 'edit' => self::can($module, 'edit')];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
private static function isEditPage(string $path): bool
|
||||
{
|
||||
return in_array(
|
||||
$path,
|
||||
[
|
||||
'/animal/new',
|
||||
'/animal/edit',
|
||||
'/animal/death',
|
||||
'/animal/surgery/new',
|
||||
'/litter/new',
|
||||
'/litter/add-existing',
|
||||
'/litter/add-existing/save',
|
||||
'/bonded/new',
|
||||
'/directory/new',
|
||||
'/directory/edit',
|
||||
'/care-round/setup',
|
||||
],
|
||||
true,
|
||||
);
|
||||
}
|
||||
public static function save(array $input, ?int $userId): void
|
||||
{
|
||||
$db = DB::pdo();
|
||||
$stmt = $db->prepare(
|
||||
"INSERT INTO role_permissions(role,module,can_view,can_edit,updated_by,updated_at) VALUES(:role,:module,:view,:edit,:user,datetime('now')) ON CONFLICT(role,module) DO UPDATE SET can_view=excluded.can_view,can_edit=excluded.can_edit,updated_by=excluded.updated_by,updated_at=excluded.updated_at",
|
||||
);
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
foreach (self::ROLES as $role => $unused) {
|
||||
if ($role === 'admin') {
|
||||
continue;
|
||||
}
|
||||
foreach (self::MODULES as $module => $unusedModule) {
|
||||
$view = isset($input[$role][$module]['view']) ? 1 : 0;
|
||||
if ($module === 'medical' && !isset($input[$role]['animals']['view'])) {
|
||||
$view = 0;
|
||||
}
|
||||
$edit = $view && isset($input[$role][$module]['edit']) ? 1 : 0;
|
||||
$stmt->execute([
|
||||
':role' => $role,
|
||||
':module' => $module,
|
||||
':view' => $view,
|
||||
':edit' => $edit,
|
||||
':user' => $userId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$db->commit();
|
||||
self::$cache = [];
|
||||
} catch (Throwable $e) {
|
||||
$db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue