forked from fa/breadcrumb-the-shire
Consolidate audit permissions to audit namespace
This commit is contained in:
@@ -1429,22 +1429,24 @@ VALUES
|
||||
('settings.update', 'Can update settings', 1, 1),
|
||||
('tenant.scope.global', 'Can bypass tenant scope globally', 1, 1),
|
||||
('imports.view', 'Can view imports', 1, 1),
|
||||
('imports.audit.view', 'Can view import audit logs', 1, 1),
|
||||
('audit.imports.view', 'Can view import audit logs', 1, 1),
|
||||
('audit.imports.purge', 'Can purge import audit logs', 1, 1),
|
||||
('jobs.view', 'Can view scheduled jobs', 1, 1),
|
||||
('jobs.manage', 'Can manage scheduled jobs', 1, 1),
|
||||
('jobs.run_now', 'Can run scheduled jobs manually', 1, 1),
|
||||
('user_lifecycle_audit.view', 'Can view user lifecycle audit logs', 1, 1),
|
||||
('audit.user_lifecycle.view', 'Can view user lifecycle audit logs', 1, 1),
|
||||
('users.import', 'Can import users', 1, 1),
|
||||
('users.import_assignments', 'Can assign tenant, role and department during user import', 1, 1),
|
||||
('users.lifecycle_restore', 'Can restore users from lifecycle audit', 1, 1),
|
||||
('audit.user_lifecycle.restore', 'Can restore users from lifecycle audit', 1, 1),
|
||||
('custom_fields.manage', 'Can manage tenant custom field definitions', 1, 1),
|
||||
('custom_fields.edit_values', 'Can edit user custom field values', 1, 1),
|
||||
('api_docs.view', 'Can view API documentation', 1, 1),
|
||||
('docs.view', 'Can view developer documentation', 1, 1),
|
||||
('mail_log.view', 'Can view mail logs', 1, 1),
|
||||
('api_audit.view', 'Can view API audit logs', 1, 1),
|
||||
('system_audit.view', 'Can view system audit logs', 1, 1),
|
||||
('system_audit.purge', 'Can purge system audit logs', 1, 1),
|
||||
('audit.api.view', 'Can view API audit logs', 1, 1),
|
||||
('audit.api.purge', 'Can purge API audit logs', 1, 1),
|
||||
('audit.system.view', 'Can view system audit logs', 1, 1),
|
||||
('audit.system.purge', 'Can purge system audit logs', 1, 1),
|
||||
('api_tokens.manage', 'Can manage user API tokens', 1, 1),
|
||||
('stats.view', 'Can view statistics', 1, 1),
|
||||
('system_info.view', 'Can view system info and health status', 1, 1),
|
||||
@@ -1542,13 +1544,13 @@ JOIN permissions p ON p.`key` IN (
|
||||
'roles.view', 'roles.create', 'roles.update', 'roles.delete',
|
||||
'permissions.view', 'permissions.create', 'permissions.update', 'permissions.delete',
|
||||
'settings.view', 'settings.update', 'tenant.scope.global',
|
||||
'imports.view', 'imports.audit.view', 'jobs.view', 'jobs.manage', 'jobs.run_now',
|
||||
'user_lifecycle_audit.view', 'users.import', 'users.import_assignments',
|
||||
'users.lifecycle_restore',
|
||||
'imports.view', 'audit.imports.view', 'audit.imports.purge', 'jobs.view', 'jobs.manage', 'jobs.run_now',
|
||||
'audit.user_lifecycle.view', 'users.import', 'users.import_assignments',
|
||||
'audit.user_lifecycle.restore',
|
||||
'custom_fields.manage', 'custom_fields.edit_values',
|
||||
'api_docs.view', 'docs.view',
|
||||
'api_tokens.manage',
|
||||
'mail_log.view', 'api_audit.view', 'system_audit.view', 'system_audit.purge',
|
||||
'mail_log.view', 'audit.api.view', 'audit.api.purge', 'audit.system.view', 'audit.system.purge',
|
||||
'stats.view', 'system_info.view',
|
||||
'roles.assign_all',
|
||||
'helpdesk.access', 'helpdesk.settings.manage', 'helpdesk.team-workload.view', 'helpdesk.risk-radar.view', 'helpdesk.software-products.manage',
|
||||
@@ -1598,9 +1600,9 @@ JOIN permissions p ON p.`key` IN (
|
||||
'address_book.view',
|
||||
'tenants.view', 'departments.view', 'roles.view', 'permissions.view',
|
||||
'settings.view',
|
||||
'imports.view', 'imports.audit.view',
|
||||
'user_lifecycle_audit.view',
|
||||
'mail_log.view', 'api_audit.view', 'system_audit.view',
|
||||
'imports.view', 'audit.imports.view',
|
||||
'audit.user_lifecycle.view',
|
||||
'mail_log.view', 'audit.api.view', 'audit.system.view',
|
||||
'stats.view', 'jobs.view'
|
||||
)
|
||||
WHERE r.code = 'AUDITOR'
|
||||
|
||||
71
db/updates/2026-04-24-audit-permission-key-consolidation.sql
Normal file
71
db/updates/2026-04-24-audit-permission-key-consolidation.sql
Normal file
@@ -0,0 +1,71 @@
|
||||
-- Idempotent update: consolidate legacy audit permission keys to audit.* namespace.
|
||||
|
||||
INSERT INTO `permissions` (`key`, `description`, `active`, `is_system`)
|
||||
VALUES
|
||||
('audit.api.view', 'Can view API audit logs', 1, 1),
|
||||
('audit.api.purge', 'Can purge API audit logs', 1, 1),
|
||||
('audit.system.view', 'Can view system audit logs', 1, 1),
|
||||
('audit.system.purge', 'Can purge system audit logs', 1, 1),
|
||||
('audit.imports.view', 'Can view import audit logs', 1, 1),
|
||||
('audit.imports.purge', 'Can purge import audit logs', 1, 1),
|
||||
('audit.user_lifecycle.view', 'Can view user lifecycle audit logs', 1, 1),
|
||||
('audit.user_lifecycle.restore', 'Can restore users from lifecycle audit', 1, 1)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`description` = VALUES(`description`),
|
||||
`active` = VALUES(`active`),
|
||||
`is_system` = VALUES(`is_system`);
|
||||
|
||||
-- Preserve existing role grants by copying role_permissions from legacy keys.
|
||||
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
|
||||
SELECT DISTINCT rp.`role_id`, p_new.`id`, NOW()
|
||||
FROM `role_permissions` rp
|
||||
JOIN `permissions` p_old ON p_old.`id` = rp.`permission_id`
|
||||
JOIN `permissions` p_new ON p_new.`key` = (
|
||||
CASE p_old.`key`
|
||||
WHEN 'api_audit.view' THEN 'audit.api.view'
|
||||
WHEN 'system_audit.view' THEN 'audit.system.view'
|
||||
WHEN 'system_audit.purge' THEN 'audit.system.purge'
|
||||
WHEN 'imports.audit.view' THEN 'audit.imports.view'
|
||||
WHEN 'user_lifecycle_audit.view' THEN 'audit.user_lifecycle.view'
|
||||
WHEN 'users.lifecycle_restore' THEN 'audit.user_lifecycle.restore'
|
||||
ELSE NULL
|
||||
END
|
||||
)
|
||||
WHERE p_old.`key` IN (
|
||||
'api_audit.view',
|
||||
'system_audit.view',
|
||||
'system_audit.purge',
|
||||
'imports.audit.view',
|
||||
'user_lifecycle_audit.view',
|
||||
'users.lifecycle_restore'
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`role_id` = `role_id`;
|
||||
|
||||
-- Keep admin baseline complete for purge/restore actions.
|
||||
INSERT INTO `role_permissions` (`role_id`, `permission_id`, `created`)
|
||||
SELECT r.`id`, p.`id`, NOW()
|
||||
FROM `roles` r
|
||||
JOIN `permissions` p ON p.`key` IN (
|
||||
'audit.api.purge',
|
||||
'audit.imports.purge',
|
||||
'audit.system.purge',
|
||||
'audit.user_lifecycle.restore'
|
||||
)
|
||||
WHERE r.`description` IN ('Admin', 'Administrator') OR r.`id` = 1
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`role_id` = `role_id`;
|
||||
|
||||
-- Legacy keys are retained for history but deactivated to avoid future drift.
|
||||
UPDATE `permissions`
|
||||
SET
|
||||
`active` = 0,
|
||||
`is_system` = 1
|
||||
WHERE `key` IN (
|
||||
'api_audit.view',
|
||||
'system_audit.view',
|
||||
'system_audit.purge',
|
||||
'imports.audit.view',
|
||||
'user_lifecycle_audit.view',
|
||||
'users.lifecycle_restore'
|
||||
);
|
||||
@@ -184,6 +184,14 @@
|
||||
"perm.jobs.view": "Geplante Aufgaben anzeigen",
|
||||
"perm.jobs.manage": "Geplante Aufgaben verwalten",
|
||||
"perm.jobs.run_now": "Geplante Aufgaben manuell ausführen",
|
||||
"perm.audit.api.view": "API-Protokolle anzeigen",
|
||||
"perm.audit.api.purge": "API-Protokolle bereinigen",
|
||||
"perm.audit.system.view": "System-Protokolle anzeigen",
|
||||
"perm.audit.system.purge": "System-Protokolle bereinigen",
|
||||
"perm.audit.imports.view": "Import-Protokolle anzeigen",
|
||||
"perm.audit.imports.purge": "Import-Protokolle bereinigen",
|
||||
"perm.audit.user_lifecycle.view": "Benutzer-Lifecycle-Protokolle anzeigen",
|
||||
"perm.audit.user_lifecycle.restore": "Benutzer aus Lifecycle-Protokoll wiederherstellen",
|
||||
"perm.user_lifecycle_audit.view": "Benutzer-Lifecycle-Protokolle anzeigen",
|
||||
"perm.users.lifecycle_restore": "Benutzer aus Lifecycle-Protokoll wiederherstellen",
|
||||
"Select all": "Alle auswählen",
|
||||
|
||||
@@ -184,6 +184,14 @@
|
||||
"perm.jobs.view": "View scheduled jobs",
|
||||
"perm.jobs.manage": "Manage scheduled jobs",
|
||||
"perm.jobs.run_now": "Run scheduled jobs manually",
|
||||
"perm.audit.api.view": "View API logs",
|
||||
"perm.audit.api.purge": "Purge API logs",
|
||||
"perm.audit.system.view": "View system logs",
|
||||
"perm.audit.system.purge": "Purge system logs",
|
||||
"perm.audit.imports.view": "View import audit logs",
|
||||
"perm.audit.imports.purge": "Purge import audit logs",
|
||||
"perm.audit.user_lifecycle.view": "View user lifecycle logs",
|
||||
"perm.audit.user_lifecycle.restore": "Restore users from lifecycle audit",
|
||||
"perm.user_lifecycle_audit.view": "View user lifecycle logs",
|
||||
"perm.users.lifecycle_restore": "Restore users from lifecycle audit",
|
||||
"Select all": "Select all",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"Telemetry helps detect recurring UI issues and failed requests in production.": "Telemetry hilft, wiederkehrende UI-Probleme und fehlgeschlagene Requests in Produktion zu erkennen.",
|
||||
"perm.audit.api.view": "API-Protokolle anzeigen",
|
||||
"perm.audit.api.purge": "API-Protokolle bereinigen",
|
||||
"perm.audit.system.view": "System-Protokolle anzeigen",
|
||||
"perm.audit.system.purge": "System-Protokolle bereinigen",
|
||||
"perm.audit.imports.view": "Import-Protokolle anzeigen",
|
||||
"perm.audit.imports.purge": "Import-Protokolle bereinigen",
|
||||
"perm.audit.user_lifecycle.view": "Benutzer-Lifecycle-Protokolle anzeigen",
|
||||
"perm.audit.user_lifecycle.restore": "Benutzer aus Lifecycle-Protokoll wiederherstellen",
|
||||
"perm.user_lifecycle_audit.view": "Benutzer-Lifecycle-Protokolle anzeigen",
|
||||
"perm.users.lifecycle_restore": "Benutzer aus Lifecycle-Protokoll wiederherstellen",
|
||||
"User lifecycle": "Benutzer-Lebenszyklus",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
{
|
||||
"Telemetry helps detect recurring UI issues and failed requests in production.": "Telemetry helps detect recurring UI issues and failed requests in production.",
|
||||
"perm.audit.api.view": "View API logs",
|
||||
"perm.audit.api.purge": "Purge API logs",
|
||||
"perm.audit.system.view": "View system logs",
|
||||
"perm.audit.system.purge": "Purge system logs",
|
||||
"perm.audit.imports.view": "View import audit logs",
|
||||
"perm.audit.imports.purge": "Purge import audit logs",
|
||||
"perm.audit.user_lifecycle.view": "View user lifecycle logs",
|
||||
"perm.audit.user_lifecycle.restore": "Restore users from lifecycle audit",
|
||||
"perm.user_lifecycle_audit.view": "View user lifecycle logs",
|
||||
"perm.users.lifecycle_restore": "Restore users from lifecycle audit",
|
||||
"User lifecycle": "User lifecycle",
|
||||
|
||||
@@ -18,14 +18,14 @@ final class AuditAuthorizationPolicy implements AuthorizationPolicyInterface
|
||||
public const ABILITY_USER_LIFECYCLE_RESTORE = 'audit.user_lifecycle.restore';
|
||||
|
||||
private const ABILITY_PERMISSION_MAP = [
|
||||
self::ABILITY_API_AUDIT_VIEW => 'api_audit.view',
|
||||
self::ABILITY_API_AUDIT_VIEW => 'audit.api.view',
|
||||
self::ABILITY_API_AUDIT_PURGE => 'audit.api.purge',
|
||||
self::ABILITY_SYSTEM_AUDIT_VIEW => 'system_audit.view',
|
||||
self::ABILITY_SYSTEM_AUDIT_PURGE => 'system_audit.purge',
|
||||
self::ABILITY_IMPORTS_AUDIT_VIEW => 'imports.audit.view',
|
||||
self::ABILITY_SYSTEM_AUDIT_VIEW => 'audit.system.view',
|
||||
self::ABILITY_SYSTEM_AUDIT_PURGE => 'audit.system.purge',
|
||||
self::ABILITY_IMPORTS_AUDIT_VIEW => 'audit.imports.view',
|
||||
self::ABILITY_IMPORTS_AUDIT_PURGE => 'audit.imports.purge',
|
||||
self::ABILITY_USER_LIFECYCLE_VIEW => 'user_lifecycle_audit.view',
|
||||
self::ABILITY_USER_LIFECYCLE_RESTORE => 'users.lifecycle_restore',
|
||||
self::ABILITY_USER_LIFECYCLE_VIEW => 'audit.user_lifecycle.view',
|
||||
self::ABILITY_USER_LIFECYCLE_RESTORE => 'audit.user_lifecycle.restore',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Tests\Module\Audit;
|
||||
|
||||
use MintyPHP\Module\Audit\AuditAuthorizationPolicy;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class AuditAuthorizationPolicyTest extends TestCase
|
||||
{
|
||||
private PermissionService&MockObject $permissionService;
|
||||
private AuditAuthorizationPolicy $policy;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->policy = new AuditAuthorizationPolicy($this->permissionService);
|
||||
}
|
||||
|
||||
public static function abilityPermissionProvider(): array
|
||||
{
|
||||
return [
|
||||
[AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW, 'audit.api.view'],
|
||||
[AuditAuthorizationPolicy::ABILITY_API_AUDIT_PURGE, 'audit.api.purge'],
|
||||
[AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_VIEW, 'audit.system.view'],
|
||||
[AuditAuthorizationPolicy::ABILITY_SYSTEM_AUDIT_PURGE, 'audit.system.purge'],
|
||||
[AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_VIEW, 'audit.imports.view'],
|
||||
[AuditAuthorizationPolicy::ABILITY_IMPORTS_AUDIT_PURGE, 'audit.imports.purge'],
|
||||
[AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_VIEW, 'audit.user_lifecycle.view'],
|
||||
[AuditAuthorizationPolicy::ABILITY_USER_LIFECYCLE_RESTORE, 'audit.user_lifecycle.restore'],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('abilityPermissionProvider')]
|
||||
public function testSupportsAllAuditAbilities(string $ability, string $permission): void
|
||||
{
|
||||
self::assertTrue($this->policy->supports($ability));
|
||||
self::assertNotSame('', $permission);
|
||||
}
|
||||
|
||||
public function testSupportsRejectsUnknownAbility(): void
|
||||
{
|
||||
self::assertFalse($this->policy->supports('audit.unknown.ability'));
|
||||
}
|
||||
|
||||
#[DataProvider('abilityPermissionProvider')]
|
||||
public function testAuthorizeUsesMappedPermissionKey(string $ability, string $permission): void
|
||||
{
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('userHas')
|
||||
->with(17, $permission)
|
||||
->willReturn(true);
|
||||
|
||||
$decision = $this->policy->authorize($ability, ['actor_user_id' => 17]);
|
||||
|
||||
self::assertTrue($decision->isAllowed());
|
||||
}
|
||||
|
||||
public function testAuthorizeDeniesMissingActorIdWithoutPermissionLookup(): void
|
||||
{
|
||||
$this->permissionService->expects($this->never())->method('userHas');
|
||||
|
||||
$decision = $this->policy->authorize(AuditAuthorizationPolicy::ABILITY_API_AUDIT_VIEW, []);
|
||||
|
||||
self::assertFalse($decision->isAllowed());
|
||||
self::assertSame(403, $decision->status());
|
||||
}
|
||||
|
||||
public function testAuthorizeDeniesUnknownAbilityWithoutPermissionLookup(): void
|
||||
{
|
||||
$this->permissionService->expects($this->never())->method('userHas');
|
||||
|
||||
$decision = $this->policy->authorize('audit.unknown.ability', ['actor_user_id' => 17]);
|
||||
|
||||
self::assertFalse($decision->isAllowed());
|
||||
self::assertSame(403, $decision->status());
|
||||
}
|
||||
|
||||
public function testPolicyPermissionKeysMatchManifestPermissionKeys(): void
|
||||
{
|
||||
$manifest = require dirname(__DIR__, 3) . '/module.php';
|
||||
$manifestPermissions = is_array($manifest['permissions'] ?? null) ? $manifest['permissions'] : [];
|
||||
|
||||
$manifestKeys = [];
|
||||
foreach ($manifestPermissions as $permission) {
|
||||
if (is_array($permission) && isset($permission['key'])) {
|
||||
$manifestKeys[] = (string) $permission['key'];
|
||||
}
|
||||
}
|
||||
sort($manifestKeys);
|
||||
|
||||
$policyKeys = [];
|
||||
foreach (self::abilityPermissionProvider() as $pair) {
|
||||
$policyKeys[] = $pair[1];
|
||||
}
|
||||
$policyKeys = array_values(array_unique($policyKeys));
|
||||
sort($policyKeys);
|
||||
|
||||
self::assertSame($manifestKeys, $policyKeys);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user