forked from fa/breadcrumb-the-shire
assign role roles
This commit is contained in:
@@ -277,6 +277,16 @@ CREATE TABLE IF NOT EXISTS `role_permissions` (
|
||||
CONSTRAINT `fk_role_permissions_permission` FOREIGN KEY (`permission_id`) REFERENCES `permissions` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `role_assignable_roles` (
|
||||
`role_id` INT UNSIGNED NOT NULL,
|
||||
`assignable_role_id` INT UNSIGNED NOT NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`role_id`, `assignable_role_id`),
|
||||
KEY `idx_rar_assignable` (`assignable_role_id`),
|
||||
CONSTRAINT `fk_rar_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_rar_assignable` FOREIGN KEY (`assignable_role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user_departments` (
|
||||
`user_id` INT UNSIGNED NOT NULL,
|
||||
`department_id` INT UNSIGNED NOT NULL,
|
||||
@@ -768,6 +778,18 @@ INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'User', 'USER', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'USER');
|
||||
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Manager', 'MANAGER', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'MANAGER');
|
||||
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Auditor', 'AUDITOR', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'AUDITOR');
|
||||
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Support', 'SUPPORT', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'SUPPORT');
|
||||
|
||||
INSERT INTO `users` (
|
||||
`uuid`,
|
||||
`first_name`,
|
||||
@@ -989,7 +1011,8 @@ VALUES
|
||||
('system_audit.view', 'Can view system audit logs', 1, 1),
|
||||
('system_audit.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)
|
||||
('stats.view', 'Can view statistics', 1, 1),
|
||||
('roles.assign_all', 'Can assign all roles (bypass assignable-roles check)', 1, 1)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
`description` = VALUES(`description`),
|
||||
`active` = VALUES(`active`),
|
||||
@@ -1089,7 +1112,8 @@ JOIN permissions p ON p.`key` IN (
|
||||
'api_docs.view', 'docs.view',
|
||||
'api_tokens.manage',
|
||||
'mail_log.view', 'api_audit.view', 'system_audit.view', 'system_audit.purge',
|
||||
'stats.view'
|
||||
'stats.view',
|
||||
'roles.assign_all'
|
||||
)
|
||||
WHERE r.description IN ('Admin', 'Administrator') OR r.id = 1
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
@@ -1101,6 +1125,68 @@ JOIN permissions p ON p.`key` IN ('users.self_update', 'address_book.view')
|
||||
WHERE r.code = 'USER' OR r.description = 'User'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- Manager: user & department management
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.create', 'users.update',
|
||||
'users.update_assignments', 'users.access_pdf', 'users.self_update',
|
||||
'users.import', 'users.import_assignments',
|
||||
'address_book.view',
|
||||
'departments.view', 'departments.create', 'departments.update',
|
||||
'roles.view',
|
||||
'custom_fields.edit_values'
|
||||
)
|
||||
WHERE r.code = 'MANAGER'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- Auditor: read-only + audit logs
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.view_audit', 'users.self_update',
|
||||
'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',
|
||||
'stats.view', 'jobs.view'
|
||||
)
|
||||
WHERE r.code = 'AUDITOR'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- Support: helpdesk / user support
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.view_audit',
|
||||
'users.update', 'users.self_update',
|
||||
'address_book.view',
|
||||
'departments.view', 'roles.view',
|
||||
'api_tokens.manage',
|
||||
'custom_fields.edit_values'
|
||||
)
|
||||
WHERE r.code = 'SUPPORT'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- Admin role can assign every role (also has roles.assign_all permission as fallback)
|
||||
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
|
||||
SELECT admin_role.id, all_roles.id
|
||||
FROM roles admin_role
|
||||
CROSS JOIN roles all_roles
|
||||
WHERE admin_role.description IN ('Admin', 'Administrator') OR admin_role.id = 1;
|
||||
|
||||
-- Manager can assign USER and SUPPORT roles
|
||||
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
|
||||
SELECT mgr.id, target.id
|
||||
FROM roles mgr
|
||||
JOIN roles target ON target.code IN ('USER', 'SUPPORT')
|
||||
WHERE mgr.code = 'MANAGER';
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `description`)
|
||||
SELECT 'default_tenant_id', CAST(t.id AS CHAR), 'setting.default_tenant'
|
||||
FROM tenants t
|
||||
|
||||
31
db/updates/2026-03-13-role-assignable-roles.sql
Normal file
31
db/updates/2026-03-13-role-assignable-roles.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Idempotent update: introduce assignable-roles mapping for privilege-escalation prevention.
|
||||
-- Each role defines which other roles its members may assign to users.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `role_assignable_roles` (
|
||||
`role_id` INT UNSIGNED NOT NULL,
|
||||
`assignable_role_id` INT UNSIGNED NOT NULL,
|
||||
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`role_id`, `assignable_role_id`),
|
||||
KEY `idx_rar_assignable` (`assignable_role_id`),
|
||||
CONSTRAINT `fk_rar_role` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_rar_assignable` FOREIGN KEY (`assignable_role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- Permission: bypass assignable-roles check (super-admin)
|
||||
INSERT INTO `permissions` (`key`, `description`, `active`, `is_system`)
|
||||
VALUES ('roles.assign_all', 'permission.roles.assign_all', 1, 1)
|
||||
ON DUPLICATE KEY UPDATE `key` = `key`;
|
||||
|
||||
-- Grant roles.assign_all to the ADMIN role
|
||||
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.`key` = 'roles.assign_all'
|
||||
WHERE r.code = 'ADMIN';
|
||||
|
||||
-- Seed: ADMIN role can assign every existing role
|
||||
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
|
||||
SELECT admin_role.id, all_roles.id
|
||||
FROM roles admin_role
|
||||
CROSS JOIN roles all_roles
|
||||
WHERE admin_role.code = 'ADMIN';
|
||||
81
db/updates/2026-03-13-seed-default-roles.sql
Normal file
81
db/updates/2026-03-13-seed-default-roles.sql
Normal file
@@ -0,0 +1,81 @@
|
||||
-- ============================================================
|
||||
-- 2026-03-13 Seed default roles: MANAGER, AUDITOR, SUPPORT
|
||||
-- Idempotent — safe to run multiple times.
|
||||
-- ============================================================
|
||||
|
||||
-- 1. Create new roles (skip if already present)
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Manager', 'MANAGER', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'MANAGER');
|
||||
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Auditor', 'AUDITOR', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'AUDITOR');
|
||||
|
||||
INSERT INTO `roles` (`uuid`, `description`, `code`, `active`, `created`)
|
||||
SELECT UUID(), 'Support', 'SUPPORT', 1, NOW()
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles WHERE code = 'SUPPORT');
|
||||
|
||||
-- 2. MANAGER permissions (15)
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.create', 'users.update',
|
||||
'users.update_assignments', 'users.access_pdf', 'users.self_update',
|
||||
'users.import', 'users.import_assignments',
|
||||
'address_book.view',
|
||||
'departments.view', 'departments.create', 'departments.update',
|
||||
'roles.view',
|
||||
'custom_fields.edit_values'
|
||||
)
|
||||
WHERE r.code = 'MANAGER'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- 3. AUDITOR permissions (18)
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.view_audit', 'users.self_update',
|
||||
'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',
|
||||
'stats.view', 'jobs.view'
|
||||
)
|
||||
WHERE r.code = 'AUDITOR'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- 4. SUPPORT permissions (10)
|
||||
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 (
|
||||
'users.view', 'users.view_meta', 'users.view_audit',
|
||||
'users.update', 'users.self_update',
|
||||
'address_book.view',
|
||||
'departments.view', 'roles.view',
|
||||
'api_tokens.manage',
|
||||
'custom_fields.edit_values'
|
||||
)
|
||||
WHERE r.code = 'SUPPORT'
|
||||
ON DUPLICATE KEY UPDATE role_id = role_id;
|
||||
|
||||
-- 5. MANAGER can assign USER and SUPPORT roles
|
||||
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
|
||||
SELECT mgr.id, target.id
|
||||
FROM roles mgr
|
||||
JOIN roles target ON target.code IN ('USER', 'SUPPORT')
|
||||
WHERE mgr.code = 'MANAGER';
|
||||
|
||||
-- 6. Ensure ADMIN assignable_roles includes the new roles
|
||||
-- (previous migration only seeded roles that existed at that time)
|
||||
INSERT IGNORE INTO `role_assignable_roles` (`role_id`, `assignable_role_id`)
|
||||
SELECT admin_role.id, new_role.id
|
||||
FROM roles admin_role
|
||||
CROSS JOIN roles new_role
|
||||
WHERE (admin_role.description IN ('Admin', 'Administrator') OR admin_role.id = 1)
|
||||
AND new_role.code IN ('MANAGER', 'AUDITOR', 'SUPPORT');
|
||||
Reference in New Issue
Block a user