Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public Publisher<AuthenticationResponse> authenticate(@Nullable HttpRequest<?> h
List<String> rolesAsString = userRoles.stream().map(Role::getRole).collect(Collectors.toList());

Map<String, Object> attributes = new HashMap<>();
attributes.put("roles", rolesAsString);
attributes.put("permissions", permissionsAsString);
attributes.put("email", memberProfile.getWorkEmail());

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
Expand All @@ -32,16 +41,26 @@ public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable Rou

if (routeMatch instanceof MethodBasedRouteMatch) {
MethodBasedRouteMatch methodBasedRouteMatch = (MethodBasedRouteMatch) routeMatch;

if (methodBasedRouteMatch.hasAnnotation(RequiredPermission.class)) {

AnnotationValue<RequiredPermission> requiredPermissionAnnotation = methodBasedRouteMatch.getAnnotation(RequiredPermission.class);
Optional<String> optionalPermission = requiredPermissionAnnotation != null ? requiredPermissionAnnotation.stringValue("value") : Optional.empty();

Map<String, Object> 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<Permission> userPermissions = new HashSet<>();

JSONArray jsonArray = new JSONArray(claims.get("roles").toString());
final List<String> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ public interface RolePermissionRepository extends CrudRepository<RolePermission,
List<RolePermission> findAll();

List<RolePermission> findByRoleId(UUID roleId);

@Query("SELECT * FROM role_permissions rp_ INNER JOIN role r_ ON rp_.roleid = r_.id WHERE role = :role")
List<RolePermission> findByRole(String role);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public interface RolePermissionServices {

List<RolePermission> findByRoleId(UUID roleId);

List<RolePermission> findByRole(String role);

List<Permission> findUserPermissions(@NotBlank UUID id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
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;
import java.util.*;
import java.util.stream.Collectors;

@Singleton
@CacheConfig("role-permission-cache")
public class RolePermissionServicesImpl implements RolePermissionServices {

private final RolePermissionRepository rolePermissionRepository;
Expand Down Expand Up @@ -53,13 +57,15 @@ public List<RolePermissionsResponseDTO> findAll() {
return roleInfo;
}

@CacheInvalidate(all = true)
@Override
public RolePermission save(UUID roleId, Permission permissionId) {
rolePermissionRepository.saveByIds(roleId.toString(), permissionId);
RolePermission saved = rolePermissionRepository.findByIds(roleId.toString(), permissionId).get(0);
return saved;
}

@CacheInvalidate(all = true)
@Override
public void delete(UUID roleId, Permission permissionId) {
rolePermissionRepository.deleteByIds(roleId.toString(), permissionId);
Expand All @@ -70,6 +76,14 @@ public List<RolePermission> findByRoleId(UUID roleId) {
return rolePermissionRepository.findByRoleId(roleId);
}


@Cacheable
@Override
public List<RolePermission> findByRole(String role) {
return rolePermissionRepository.findByRole(role);
}

@Cacheable
@Override
public List<Permission> findUserPermissions(@NotBlank UUID id) {

Expand Down
4 changes: 4 additions & 0 deletions server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ micronaut:
expire-after-write: 300
heap:
max-entries: 600
role-permission-cache:
expire-after-write: 86400

router:
static-resources:
Expand Down Expand Up @@ -128,6 +130,8 @@ ehcache:
enabled: true
member-cache:
enabled: true
role-permission-cache:
enabled: true
---
mail-jet:
from_address: ${ FROM_ADDRESS }
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -34,14 +35,22 @@
public class SecurityRuleResultTest {

List<String> 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<String> userRoles = List.of("ADMIN");

@Inject
PermissionSecurityRule permissionSecurityRule;

@Inject
RolePermissionServices rolePermissionServices;

@Inject
RoleServices roleServices;

@Mock
private MethodBasedRouteMatch mockMethodBasedRouteMatch;

Expand All @@ -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);
}

Expand All @@ -67,11 +80,13 @@ public void allowSecurityRuleResultTest() {

Map<String, Object> 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);

Expand All @@ -93,6 +108,7 @@ public void rejectSecurityRuleResultTest() {

Map<String, Object> attributes = new HashMap<>();
attributes.put("permissions", userPermissions);
attributes.put("roles", userRoles);
attributes.put("email", "test.email.address");

when(mockMethodBasedRouteMatch.hasAnnotation(RequiredPermission.class)).thenReturn(true);
Expand Down