Bug Report
Summary
AuthorityService.getAuthoritiesFromRoles() only maps privilege names to GrantedAuthority objects — it does not include the role names themselves (e.g., ROLE_ADMIN). This causes the framework's own @PreAuthorize("hasRole('ADMIN')") annotations (on methods like UserEmailService.initiateAdminPasswordReset()) to always deny access, even for users who have the ROLE_ADMIN role.
Root Cause
In AuthorityService.getAuthoritiesFromRoles(), the stream pipeline does:
roles.stream()
.flatMap(role -> role.getPrivileges().stream()) // only privileges
.map(Privilege::getName)
.map(SimpleGrantedAuthority::new)
.collect(toSet());
This produces authorities like ADMIN_PRIVILEGE, LOGIN_PRIVILEGE, etc. — but never ROLE_ADMIN or ROLE_USER.
Meanwhile, UserEmailService.initiateAdminPasswordReset() (all overloads) is annotated with @PreAuthorize("hasRole('ADMIN')"), which checks for ROLE_ADMIN as a granted authority. Since ROLE_ADMIN is never granted, the call always fails with AuthorizationDeniedException: Access Denied.
Impact
Any consumer using UserEmailService.initiateAdminPasswordReset() gets a 500 error. The only workaround is to add ROLE_ADMIN as an explicit privilege name for the ROLE_ADMIN role in the config:
user:
roles:
roles-and-privileges:
"[ROLE_ADMIN]":
- ROLE_ADMIN # workaround: needed for framework's hasRole() checks
- ADMIN_PRIVILEGE
Suggested Fix
AuthorityService.getAuthoritiesFromRoles() should include both role names and privilege names as granted authorities:
public Collection<? extends GrantedAuthority> getAuthoritiesFromRoles(Collection<Role> roles) {
Set<GrantedAuthority> authorities = new HashSet<>();
for (Role role : roles) {
// Include the role itself (needed for hasRole() checks)
authorities.add(new SimpleGrantedAuthority(role.getName()));
// Include all privileges for the role
for (Privilege privilege : role.getPrivileges()) {
authorities.add(new SimpleGrantedAuthority(privilege.getName()));
}
}
return authorities;
}
This is the standard Spring Security convention — UserDetailsService implementations typically include both role names and privilege names as granted authorities.
Environment
- DS Spring User Framework version: 4.3.0
- Spring Security version: 7.0.3
- Discovered in: MagicMenu admin password reset feature (MM-207)
Bug Report
Summary
AuthorityService.getAuthoritiesFromRoles()only maps privilege names toGrantedAuthorityobjects — it does not include the role names themselves (e.g.,ROLE_ADMIN). This causes the framework's own@PreAuthorize("hasRole('ADMIN')")annotations (on methods likeUserEmailService.initiateAdminPasswordReset()) to always deny access, even for users who have theROLE_ADMINrole.Root Cause
In
AuthorityService.getAuthoritiesFromRoles(), the stream pipeline does:This produces authorities like
ADMIN_PRIVILEGE,LOGIN_PRIVILEGE, etc. — but neverROLE_ADMINorROLE_USER.Meanwhile,
UserEmailService.initiateAdminPasswordReset()(all overloads) is annotated with@PreAuthorize("hasRole('ADMIN')"), which checks forROLE_ADMINas a granted authority. SinceROLE_ADMINis never granted, the call always fails withAuthorizationDeniedException: Access Denied.Impact
Any consumer using
UserEmailService.initiateAdminPasswordReset()gets a 500 error. The only workaround is to addROLE_ADMINas an explicit privilege name for theROLE_ADMINrole in the config:Suggested Fix
AuthorityService.getAuthoritiesFromRoles()should include both role names and privilege names as granted authorities:This is the standard Spring Security convention —
UserDetailsServiceimplementations typically include both role names and privilege names as granted authorities.Environment