Roles and permissions — redesign technical guide
Scope and evidence
Reviewed 15 October 2026 against local working trees of smartmobility-legacy, smartmobility-nextgen-back and smartmobility-nextgen-front. References below are repository-relative paths. This is code inspection; existing automated scenarios are cited as supporting specifications and were not executed. No production configuration, live authorization test or complete security audit is claimed.
The functional guide describes user behavior; the configuration guide describes administration.
Nextgen data model
Entities live under src/Infrastructure/Persistence/Entity/:
| Entity / relation | Purpose |
|---|---|
AccessControl/Role | Company-scoped role, localized content, optional company-unique code |
AccessControl/Right | Permission catalogue entry and slug |
AccessControl/RoleRight | Enables a right on a role |
AccessControl/UserRole | Direct user membership |
Group/UserGroupRole | Role assignment through a user group |
AccessControl/RoleRightVisibilityGroup | Visibility groups attached to a particular enabled permission |
Role::visibilityGroups / role_visibility_group | Role-level visibility defaults |
Effective permission resolution
src/Infrastructure/Persistence/Repository/AccessControl/RoleRightRepository.php::findUserRoleRightsWithVisibilityGroups combines direct role rights and user-group-derived role rights using SQL UNION. Deleted user groups are excluded from the group branch; deleted visibility groups contribute no visibility ID. The result is filtered by current user and permission slug.
src/Infrastructure/Persistence/Service/AccessControl/UserRightManager.php implements Domain/V2/AccessControl/Contract/UserAccessResolver. It caches rows by slug and merges visibility IDs for the same permission across grants. hasAccessFor succeeds when a right is found; this is separate from data filtering.
src/Infrastructure/Security/Voter/ResourceConfigurationRightVoter.php calls that resolver for a resource configuration and dispatches AllowedRightEvent after an allowed permission check. UserRightManager::onAllowedRight sets the current scope from that permission's visibility groups, then adds the current user's visibility-group assignments. Hierarchical relationship IDs are derived from the resolved groups when requested.
The resolver treats a user assigned to a visibility group with internal code SUPER_ADMIN specially: its allowed visibility group array is empty. Interpret this together with the consuming resource's filtering implementation; an empty array is not a universal deny-all contract. This branch does not itself manufacture a missing action permission.
System security roles from src/Infrastructure/Security/ACL/RoleProvider.php are separate: ROLE_USER, manager/master-manager status and the admin-derived super-admin role are computed from user properties. A configurable business role title does not establish those properties.
Scope propagation and lifecycle
src/Infrastructure/Persistence/Listener/AccessControl/RoleListener.php and src/Infrastructure/Persistence/Service/AccessControl/RoleManager.php implement:
- Role visibility attachment: propagate attached groups to all enabled role rights, deduplicated.
- Permission attachment: propagate the role's current visibility defaults to enabled rights.
- Permission visibility detachment: remove the same group from role defaults, without clearing other permission-specific copies in that listener.
- Autoconfiguration: update rights from a preset, then propagate role defaults.
- Deletion: reject a role that
RoleRepository::findUnusedRolecannot find as unused; both direct user and user-group relations must be absent.
No symmetrical post-detach-role-default listener clearing all permission scopes was found in the inspected listener. Consumers must read stored permission attachments after changing defaults.
cloneRole creates a role in the same company, clones translations and adds each enabled right. It does not copy code, recipient relations, role defaults or permission-specific scope relations. reset removes permission visibility links and role rights and sets the permission count to zero; it leaves role defaults and population intact.
Presets are EMPLOYEE, MANAGER, HR and ADMIN in src/Domain/V2/AccessControl/Enum/RolePresetEnum.php. RoleAutoconfigureDTO accepts preset and nullable strict. Truthy strict mode removes rights outside the preset and their visibility links; non-strict mode adds missing preset rights. The frontend explicitly requests strict mode.
Nextgen API contract
The following paths are relative to /api/v2. Routing sources: src/Http/Api/Routing/V2/role.yaml, role_permission.yaml and role_permission_visibility_groups.yaml.
| Method and path | Permission | Purpose |
|---|---|---|
GET /role, GET /role/{id} | read-role | List / detail |
POST /role | create-role | Create |
PUT or PATCH /role/{id} | update-role | Update content |
DELETE /role/{id} | delete-role | Delete unused role |
POST /role/{id}/clone | create-role | Clone content and enabled rights |
POST /role/{id}/reset | update-role | Clear enabled rights and permission scopes |
POST /role/{id}/autoconfigure | update-role | Apply preset |
POST / DELETE /role/{id}/permission | update-role | Attach / detach rights |
POST / DELETE /role/{id}/user | add-role-to-user / remove-role-from-user | Direct membership |
GET /role/{id}/user-group | read-role | Attached groups |
POST / DELETE /role/{id}/user-group | add-role-to-user-group / remove-role-from-user-group | Group membership |
GET /role/{id}/population | read-role | Direct and group-derived recipients |
POST / DELETE /role/{id}/visibility-group | update-role | Role defaults |
GET /role/{roleId}/permission/{permissionId}/visibility-groups | read-role | Permission visibility attachments |
POST / DELETE /role/{roleId}/permission/{permissionId}/visibility-groups | update-role | Change permission scope |
POST /role/admin/{userId} | ROLE_SUPER_ADMIN | Privileged admin initialization |
Relation attachment/detachment requests use resource IDs, for example:
{"resources": [12, 34]}
These IDs refer to the relation target, not a permission slug. Confirm IDs in the current company. RoleRightRepository::findRoleRights joins the parent role with the current company when resolving permission-specific scopes.
The role list defaults to title ascending and accepts title, code, permission count, creation-date and creator filters declared in routing. The population endpoint defaults to 50 results per page and username ascending; its declared username filter supports contain and equal. Pagination and filters affect returned data, not role grants.
Frontend implementation
Sources under src/modules/Users/Roles/ in smartmobility-nextgen-front:
| Source | Behavior |
|---|---|
roles.constants.ts | Explicit role-management permission slugs |
components/RoleCreateForm.vue | Required name, optional description, company-locale payload |
RolesDetail/RolesDetailSettings/RolesDetailSettings.vue | Permissions and Population tabs |
RolesDetail/RolesDetailSettings/RolesDetailSettingsPermissions/RolesDetailSettingsPermissionsActions.vue | Translated-label search, strict preset call, backup and restore requests |
api/roles.api.ts | Role and relation requests |
Permission search trims and lowercases the query and keeps modules containing matching translated permission titles. Preset restoration reconciles permission IDs and their group IDs through multiple requests, with resource batches of 100. It is not a single transactional rollback; validate the result after partial errors. The backup is frontend state, not an established persistent audit log.
Legacy/nextgen integration
Legacy src/Service/V2/SmrPermissionService.php checks the hybrid feature switch and retrieves nextgen permission slugs through Mk4UserRightService, caching per user within the service. A disabled switch or switch-check exception returns an empty list; this does not describe all adapter error handling. src/Service/V2/RoleService.php delegates administrator initialization to Mk4AdminService and expects a success response of 1.
These services do not establish automatic equivalence between legacy right codes and nextgen role permissions, or synchronization of populations and visibility scopes.
Supporting scenarios and validation limits
Existing backend specifications under tests/Behat/Features/V2/access_control/role/ cover CRUD, permission attachment, user and user-group attachment, consolidated population, cloning, reset, presets, visibility groups and administration. attach_visibility_group.feature explicitly covers propagation to an enabled permission and removal from role defaults after a permission-specific detach. Permission-scope attachment scenarios also exist under access_control/role_permission/.
Legacy supporting tests include tests/Integration/Security/PermissionVoterTest.php, tests/Unit/EventListener/PermissionGateListenerTest.php and tests/Unit/Service/V2/SmrPermissionServiceTest.php. These files were identified, not run for this documentation change.
For deployed acceptance, test ordinary users through list, detail, write and export paths of each target module. Neither a menu condition nor the role-management contract establishes identical scope enforcement across all resource implementations.
Architecture and request flow
Audience: technical administrators and integration teams. Verified in code: the following flow combines the legacy shell extension, frontend role API client and backend resource voter. It separates action checks from resource-specific data filtering.
Frontend src/router/private-routes.ts mounts Roles and Role Detail as authenticated routes. src/router/guards.ts::requiresAuth bootstraps session, user and permissions, checks admin/personal-space access, and evaluates route permissions when metadata declares them. The role routes declare authentication; their action enforcement must not be inferred from route metadata alone. Action visibility is separately guarded in roles.constants.ts consumers and writes are authorized by the backend route configuration.
Legacy src/Twig/Company/CompanyNavigationExtension.php::getSmrUsersRolesFeature returns the role feature for displayable admin space, using src/Service/V2/SmrDisplayService.php::isAdminSpaceDisplayable. isDefinitionBlocked can consider feature switches, profile restrictions/right lists and nextgen scope checks. Therefore “legacy controls menu, nextgen controls actions” is documentary guidance, not the full current navigation algorithm. Observed in application corroboration is limited to the functional guide; screenshots are intentionally absent here.
API payloads and validation
Verified in code: creation uses localized titles and optional descriptions; the code is optional and company-unique. Illustrative payload, using no real tenant identifiers:
{
"titles": {"en-US": "Regional HR"},
"descriptions": {"en-US": "Employee reading for the regional HR team"},
"code": "regional_hr"
}
The frontend requires a nonempty name and validates its configured maximum length in components/RoleCreateForm.vue. Backend DTO and company-scoped repository validation remain separate from this UI validation. Do not take the example as permission to create a role on a live tenant.
Preset example for POST /api/v2/role/{id}/autoconfigure:
{"preset": "HR", "strict": true}
src/modules/Users/Roles/api/roles.api.ts::autoconfigureRole uppercases the selected frontend preset. A null preset delegates to the catalogue's all-rights branch in RightRepository::findPresetRightsByRole; null must not be treated as an Employee preset. The deployed full catalogue and default preset content are Not verified.
Platform activation and runtime dependencies
Verified in code: legacy src/EventListener/FeatureSwitchListener.php defines HYBRID_MODE_SLUG and THEME_BO_V3_SLUG. themeV3Enabled persists V2 compatibility theme flags. hybridPlatformEnabled publishes hybrid configuration through SMRConfigurationPublisher before attempting administrator initialization when an impersonated user is available. Publication and administrator provisioning are distinct effects; successful initialization is not established merely by the theme being active.
The role editor depends on authenticated session bootstrap, the permission catalogue, locale/translation loading and access to the role API. Membership-derived rights depend on persisted user-group membership; no universal group-recalculation delay was established. Scope resolution in UserRightManager caches by permission within the service; the lifetime across deployed requests/workers and universal grant-refresh timing were Not verified. Do not promise an exact propagation SLA.
AIHub boundary: repository paths were verified and aihub/src/auth/security.py inspected. It defines its own API-key/JWT request authentication boundary. No role-editor caller or source evidence establishing AIHub as the nextgen role or visibility authority was found in the scoped search. It is not included as a dependency in the verified role flow; this is a bounded negative finding, not proof that every AIHub integration is unrelated to permissions.
Failure modes and observability
| Failure | Verified code behavior / diagnostic boundary |
|---|---|
| Missing role-management permission | Route permissions reject unauthorized operations; existing Behat specifications assert 403 for unauthorized permission/visibility mutations. The scenarios were not executed here. |
| Unknown visibility-group ID | Existing attachment specifications expect validation failure with a resource-not-found field message. Do not reuse IDs between tenants. |
| Role not found for population | src/Http/Api/Controller/V2/RoleController.php::population throws a not-found response when the role cannot be resolved. |
| Delete role still assigned | RoleListener::preDelete rejects with “Current Role is not deletable.” while direct or group links remain. |
| Initialize admin for disabled user | RoleController::initializeAdmin rejects a disabled user. This is not the ordinary customer role-create action. |
| Permission toggle fails | _handleTogglePermission in RolesDetailSettingsPermissions.vue logs the error and reverts the displayed state; server-confirmed state should be reloaded before retrying. |
| Preset restoration partly fails | Frontend logs an error, shows failure feedback and uses multiple requests; rollback atomicity is not established. |
| Hybrid admin initialization fails | Legacy FeatureSwitchListener::hybridPlatformEnabled logs “Failed to initialize admin role on hybrid platform”. A preceding publication/activation may already have happened. |
| Hybrid switch check fails | SmrPermissionService logs the switch-check exception and returns no retrieved rights; adapter-call failures are a separate boundary. |
For diagnosis, record generation, space, action, affected role and whether the denial came from shell display, API action or resource scope. Use authorized application logging; omit credentials and employee records from support exports. No role-specific centralized audit log, durable preset history or monitoring dashboard was established by this investigation.
Security, privacy and extension points
Verified in code: action grants and visibility scope are distinct. Direct and group-derived grants combine; there is no explicit deny overriding every other role in the inspected resolver. A restrictive role cannot be assumed to cancel a broader grant. Campaign membership and resource-specific validators remain independent.
For a new action, the relevant extension surfaces are the permission catalogue and preset mapping, backend route permission declarations, resource-specific scope enforcement, frontend action guards and translations. Confirm the consuming resource uses the scope resolver; attaching a visibility group is not proof of enforcement on an unrelated list, detail or export path. Extend the existing permission-denial and scope scenarios as appropriate for application changes; this documentation change does not modify those applications.
Client-safe boundary: no account-switch addresses, credentials, employee data or internal support access paths belong in examples or screenshots. The technical guide intentionally contains no application screenshot embeds or generated screenshot assets.
Documentary evidence and unresolved differences
Documented in Confluence: SMR - Roles & permission, version 4, 10 April 2026, describes end-user exposure intent; Back-office V3, version 2, 8 July 2026, and Playbook SMR, version 3, 24 September 2026, describe coexistence and setup. The April exclusion statement is not a verified removal of legacy persistence or voters. The playbook's default hierarchical scope and “one group = one role” recommendation are not universal resolver rules or database cardinalities. Its “no Super Admin” customer guidance does not erase privileged identities implemented in backend code.
The Visibility System, version 9, 11 October 2023, concerns historical scope behavior; it does not establish current nextgen filtering contracts.
The supplied KBCT entry returned 404. Both supplied PS entries were read and verified in PS: Delivery Philosophy & Project Methodology, version 7, 30 December 2025, and Onboarding/learning and partner certification, version 9, 18 February 2026. Neither defines an endpoint, default or runtime guarantee.
Deployment versions, synchronization completeness, cross-request caches, migration/rollback guarantees and every downstream resource's population enforcement remain Not verified. No commits were attributed as the deployed version. Application inspection is supplementary and documented in the functional/configuration guides; implementation claims here remain anchored to local paths and symbols.
Functional-guide evidence
These implementation and product references support the functional guide. Local source behavior does not establish which revision or optional features are deployed. Application observations remain distinct from code and documentary evidence.
Verified in code: legacy src/Security/PermissionVoter.php, src/Security/Voter/BaseVoter.php, src/EventListener/PermissionGateListener.php, src/Twig/Company/CompanyNavigationExtension.php; backend nextgen RoleManager, UserRightManager, RoleRightRepository and role routes; frontend src/modules/Users/Roles/. Precise paths and symbols are in the technical guide.
Documented in Confluence: SMR - Roles & permission, version 4, 10 April 2026; Back-office V3, version 2, 8 July 2026; Playbook - SmartMobility Redesign (SMR), version 3, 24 September 2026. The April page expresses exclusion of the old role system from end-user exposure; later guidance describes coexistence. The embedded live screen and local integration code support coexistence for the inspected context, not automatic migration of every tenant. The playbook's default hierarchy description was not verified as a universal resource rule.
Journey verification — 26 October 2026
Observed in application: PS Testing 2026, Jim Carter. Created role 112 with descriptive metadata only. Blank-name validation was observed. A fresh load retained title/description and 0 people, 0 activated permissions, 0 associated visibility groups. No access grant, preset or recipient was changed. User menu → Configuration returned an authorization denial at /company/account/setting.
Dernière mise à jour : 3 novembre 2026