diff --git a/server/src/main/java/com/objectcomputing/checkins/security/LocalUserPasswordAuthProvider.java b/server/src/main/java/com/objectcomputing/checkins/security/LocalUserPasswordAuthProvider.java index 5383143e71..81e2404a44 100644 --- a/server/src/main/java/com/objectcomputing/checkins/security/LocalUserPasswordAuthProvider.java +++ b/server/src/main/java/com/objectcomputing/checkins/security/LocalUserPasswordAuthProvider.java @@ -81,6 +81,7 @@ public Publisher authenticate(@Nullable HttpRequest h List rolesAsString = userRoles.stream().map(Role::getRole).collect(Collectors.toList()); Map attributes = new HashMap<>(); + attributes.put("roles", rolesAsString); attributes.put("permissions", permissionsAsString); attributes.put("email", memberProfile.getWorkEmail()); diff --git a/server/src/main/java/com/objectcomputing/checkins/security/permissions/PermissionSecurityRule.java b/server/src/main/java/com/objectcomputing/checkins/security/permissions/PermissionSecurityRule.java index bc176197e4..3b0b6ac929 100644 --- a/server/src/main/java/com/objectcomputing/checkins/security/permissions/PermissionSecurityRule.java +++ b/server/src/main/java/com/objectcomputing/checkins/security/permissions/PermissionSecurityRule.java @@ -1,6 +1,8 @@ package com.objectcomputing.checkins.security.permissions; +import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.permissions.RequiredPermission; +import com.objectcomputing.checkins.services.role.role_permissions.RolePermissionServices; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Nullable; import io.micronaut.http.HttpRequest; @@ -10,19 +12,26 @@ import io.micronaut.security.rules.SecurityRuleResult; import io.micronaut.web.router.MethodBasedRouteMatch; import io.micronaut.web.router.RouteMatch; +import org.json.JSONArray; import jakarta.inject.Singleton; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.stream.Collectors; @Singleton public class PermissionSecurityRule implements SecurityRule { public static final Integer ORDER = SecuredAnnotationRule.ORDER - 100; + public final RolePermissionServices rolePermissionServices; + + public PermissionSecurityRule(RolePermissionServices rolePermissionServices) { + this.rolePermissionServices = rolePermissionServices; + } + public int getOrder() { return ORDER; } @@ -32,16 +41,26 @@ public Publisher check(HttpRequest request, @Nullable Rou if (routeMatch instanceof MethodBasedRouteMatch) { MethodBasedRouteMatch methodBasedRouteMatch = (MethodBasedRouteMatch) routeMatch; - if (methodBasedRouteMatch.hasAnnotation(RequiredPermission.class)) { + AnnotationValue requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(RequiredPermission.class); Optional optionalPermission = requiredPermissionAnnotation != null ? requiredPermissionAnnotation.stringValue("value") : Optional.empty(); Map claims = authentication != null ? authentication.getAttributes() : null; - if (optionalPermission.isPresent() && claims != null && claims.containsKey("permissions")) { - final String requiredPermission = optionalPermission.get(); - final String userPermissions = claims.get("permissions").toString(); + if (optionalPermission.isPresent() && claims != null && claims.containsKey("roles")) { + + final Permission requiredPermission = Permission.valueOf(optionalPermission.get()); + final Set userPermissions = new HashSet<>(); + + JSONArray jsonArray = new JSONArray(claims.get("roles").toString()); + final List roles = jsonArray.toList().stream().map(Object::toString).collect(Collectors.toList()); + + + roles.forEach(role -> rolePermissionServices.findByRole(role) + .forEach(rolePermission -> userPermissions.add(rolePermission.getPermission())) + ); + return Mono.just(userPermissions.contains(requiredPermission) ? SecurityRuleResult.ALLOWED diff --git a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionRepository.java b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionRepository.java index a5fb2a148c..3b99ac5882 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionRepository.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionRepository.java @@ -33,4 +33,7 @@ public interface RolePermissionRepository extends CrudRepository findAll(); List findByRoleId(UUID roleId); + + @Query("SELECT * FROM role_permissions rp_ INNER JOIN role r_ ON rp_.roleid = r_.id WHERE role = :role") + List findByRole(String role); } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServices.java b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServices.java index 0bbe2b747b..cd7659d5bb 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServices.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServices.java @@ -15,5 +15,7 @@ public interface RolePermissionServices { List findByRoleId(UUID roleId); + List findByRole(String role); + List findUserPermissions(@NotBlank UUID id); } diff --git a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServicesImpl.java b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServicesImpl.java index 2a274d5453..9639d9c1ad 100644 --- a/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServicesImpl.java +++ b/server/src/main/java/com/objectcomputing/checkins/services/role/role_permissions/RolePermissionServicesImpl.java @@ -6,6 +6,9 @@ import com.objectcomputing.checkins.services.role.Role; import com.objectcomputing.checkins.services.role.RoleServices; +import io.micronaut.cache.annotation.CacheConfig; +import io.micronaut.cache.annotation.CacheInvalidate; +import io.micronaut.cache.annotation.Cacheable; import jakarta.inject.Singleton; import javax.validation.constraints.NotBlank; @@ -13,6 +16,7 @@ import java.util.stream.Collectors; @Singleton +@CacheConfig("role-permission-cache") public class RolePermissionServicesImpl implements RolePermissionServices { private final RolePermissionRepository rolePermissionRepository; @@ -53,6 +57,7 @@ public List findAll() { return roleInfo; } + @CacheInvalidate(all = true) @Override public RolePermission save(UUID roleId, Permission permissionId) { rolePermissionRepository.saveByIds(roleId.toString(), permissionId); @@ -60,6 +65,7 @@ public RolePermission save(UUID roleId, Permission permissionId) { return saved; } + @CacheInvalidate(all = true) @Override public void delete(UUID roleId, Permission permissionId) { rolePermissionRepository.deleteByIds(roleId.toString(), permissionId); @@ -70,6 +76,14 @@ public List findByRoleId(UUID roleId) { return rolePermissionRepository.findByRoleId(roleId); } + + @Cacheable + @Override + public List findByRole(String role) { + return rolePermissionRepository.findByRole(role); + } + + @Cacheable @Override public List findUserPermissions(@NotBlank UUID id) { diff --git a/server/src/main/resources/application.yml b/server/src/main/resources/application.yml index b77946ef0b..032ac5a269 100755 --- a/server/src/main/resources/application.yml +++ b/server/src/main/resources/application.yml @@ -14,6 +14,8 @@ micronaut: expire-after-write: 300 heap: max-entries: 600 + role-permission-cache: + expire-after-write: 86400 router: static-resources: @@ -128,6 +130,8 @@ ehcache: enabled: true member-cache: enabled: true + role-permission-cache: + enabled: true --- mail-jet: from_address: ${ FROM_ADDRESS } diff --git a/server/src/test/java/com/objectcomputing/checkins/security/SecurityRuleResultTest.java b/server/src/test/java/com/objectcomputing/checkins/security/SecurityRuleResultTest.java index dde5474fe3..68e0b938ad 100644 --- a/server/src/test/java/com/objectcomputing/checkins/security/SecurityRuleResultTest.java +++ b/server/src/test/java/com/objectcomputing/checkins/security/SecurityRuleResultTest.java @@ -1,8 +1,12 @@ package com.objectcomputing.checkins.security; import com.objectcomputing.checkins.security.permissions.PermissionSecurityRule; +import com.objectcomputing.checkins.services.permissions.Permission; import com.objectcomputing.checkins.services.permissions.RequiredPermission; +import com.objectcomputing.checkins.services.role.Role; +import com.objectcomputing.checkins.services.role.RoleServices; import com.objectcomputing.checkins.services.role.RoleType; +import com.objectcomputing.checkins.services.role.role_permissions.RolePermissionServices; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.http.HttpRequest; import io.micronaut.security.authentication.Authentication; @@ -21,10 +25,7 @@ import org.reactivestreams.Publisher; import reactor.test.StepVerifier; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.when; @@ -34,14 +35,22 @@ public class SecurityRuleResultTest { List userPermissions = List.of( - "CAN_VIEW_FEEDBACK", - "CAN_CREATE_FEEDBACK", - "CAN_DELETE_FEEDBACK" + "CAN_VIEW_FEEDBACK_REQUEST", + "CAN_CREATE_FEEDBACK_REQUEST", + "CAN_DELETE_FEEDBACK_REQUEST" ); + List userRoles = List.of("ADMIN"); + @Inject PermissionSecurityRule permissionSecurityRule; + @Inject + RolePermissionServices rolePermissionServices; + + @Inject + RoleServices roleServices; + @Mock private MethodBasedRouteMatch mockMethodBasedRouteMatch; @@ -50,6 +59,10 @@ public class SecurityRuleResultTest { @BeforeAll void initMocksAndInitializeFile() { + + Role role = roleServices.save(new Role(RoleType.ADMIN.name(), "Admin Role")); + rolePermissionServices.save(role.getId(), Permission.CAN_VIEW_FEEDBACK_REQUEST); + MockitoAnnotations.openMocks(this); } @@ -67,11 +80,13 @@ public void allowSecurityRuleResultTest() { Map attributes = new HashMap<>(); attributes.put("permissions", userPermissions); + attributes.put("roles", userRoles); attributes.put("email", "test.email.address"); + when(mockMethodBasedRouteMatch.hasAnnotation(RequiredPermission.class)).thenReturn(true); when(mockMethodBasedRouteMatch.getAnnotation(RequiredPermission.class)).thenReturn(mockRequiredPermissionAnnotation); - when(mockRequiredPermissionAnnotation.stringValue("value")).thenReturn(Optional.of("CAN_VIEW_FEEDBACK")); + when(mockRequiredPermissionAnnotation.stringValue("value")).thenReturn(Optional.of("CAN_VIEW_FEEDBACK_REQUEST")); Authentication auth = Authentication.build("test.email.address", attributes); @@ -93,6 +108,7 @@ public void rejectSecurityRuleResultTest() { Map attributes = new HashMap<>(); attributes.put("permissions", userPermissions); + attributes.put("roles", userRoles); attributes.put("email", "test.email.address"); when(mockMethodBasedRouteMatch.hasAnnotation(RequiredPermission.class)).thenReturn(true);