feat: Initial implementation of permission manager SDK - Add core permission management functionality with @RequiresPermission annotation - Implement permission checking aspect with Spring Security integration - Add comprehensive model classes for permissions, roles, and domains - Create integration builder for permission structure setup - Add configuration support for permission manager client - Implement exception handling for access control - Add extensive test coverage with integration tests - Configure Maven build with Spring Boot/Cloud dependencies

This commit is contained in:
2025-01-08 02:32:57 +01:00
parent f039652d4b
commit 6d4a3e2ea5
48 changed files with 2816 additions and 52 deletions

View File

@ -0,0 +1,254 @@
package de.mummeit.pmg.api;
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
import de.mummeit.pmg.api.model.access.request.Permit;
import de.mummeit.pmg.api.model.access.request.PermitRequest;
import de.mummeit.pmg.api.model.access.request.RevokeScopeAccessRequest;
import de.mummeit.pmg.api.model.access.request.RevokeUserAccessRequest;
import de.mummeit.pmg.api.model.access.request.SearchPermitRequest;
import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.integration.DomainIntegration;
import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.integration.PermissionIntegration;
import de.mummeit.pmg.api.model.integration.RoleIntegration;
import de.mummeit.pmg.api.model.integration.RolePermissionRelationIntegration;
import de.mummeit.pmg.api.model.structure.Domain;
import de.mummeit.pmg.api.model.structure.Permission;
import de.mummeit.pmg.api.model.structure.Role;
import de.mummeit.utility.BaseIntegrationTest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class PermissionManagerClientIntegrationTest extends BaseIntegrationTest {
@Autowired
private PermissionManagerClient permissionManagerClient;
private static final String TEST_DOMAIN = "test-domain";
private static final String TEST_PERMISSION = "test-permission";
private static final String TEST_ROLE = "test-role";
private static final String TEST_USER = "test-user";
private static final String TEST_SCOPE = "test-scope";
@Test
@DisplayName("should return health status")
void getHealthStatus() {
String healthStatus = permissionManagerClient.getHealthStatus();
assertNotNull(healthStatus);
}
@Test
@DisplayName("should successfully perform integrations")
void integrationShouldRun() {
List<Integration<?>> integrations = List.of(
DomainIntegration.builder()
.id("1")
.action(Integration.Action.create)
.data(DomainIntegration.Data.builder()
.name("test")
.description("test")
.build())
.build(),
PermissionIntegration.builder()
.id("2")
.action(Integration.Action.create)
.data(PermissionIntegration.Data.builder()
.domain("test")
.name("test")
.description("test")
.build())
.build(),
RoleIntegration.builder()
.id("3")
.action(Integration.Action.create)
.data(RoleIntegration.Data.builder()
.domain("test")
.name("test")
.description("test")
.build())
.build(),
RolePermissionRelationIntegration.builder()
.id("4")
.action(Integration.Action.create)
.data(RolePermissionRelationIntegration.Data.builder()
.domain("test")
.role("test")
.permissions(List.of("test"))
.build())
.build()
);
assertDoesNotThrow(() -> permissionManagerClient.performIntegration(integrations));
}
@Test
@DisplayName("should manage domains successfully")
void domainManagement() {
// Create domain
Domain domain = new Domain();
domain.setName(TEST_DOMAIN);
domain.setDescription("Test Domain Description");
Domain createdDomain = permissionManagerClient.createDomain(domain);
assertNotNull(createdDomain);
assertEquals(TEST_DOMAIN, createdDomain.getName());
// Get domain
Domain retrievedDomain = permissionManagerClient.getDomain(TEST_DOMAIN);
assertNotNull(retrievedDomain);
assertEquals(TEST_DOMAIN, retrievedDomain.getName());
// Update domain
domain.setDescription("Updated Description");
Domain updatedDomain = permissionManagerClient.updateDomain(TEST_DOMAIN, domain);
assertNotNull(updatedDomain);
assertEquals("Updated Description", updatedDomain.getDescription());
// Delete domain
assertDoesNotThrow(() -> permissionManagerClient.deleteDomain(TEST_DOMAIN));
}
@Test
@DisplayName("should manage permissions successfully")
void permissionManagement() {
// First create a domain
Domain domain = new Domain();
domain.setName(TEST_DOMAIN);
domain.setDescription("Test Domain Description");
permissionManagerClient.createDomain(domain);
// Create permission
Permission permission = new Permission();
permission.setName(TEST_PERMISSION);
permission.setDescription("Test Permission Description");
Permission createdPermission = permissionManagerClient.createPermission(TEST_DOMAIN, permission);
assertNotNull(createdPermission);
assertEquals(TEST_PERMISSION, createdPermission.getName());
// Get permission
Permission retrievedPermission = permissionManagerClient.getPermission(TEST_DOMAIN, TEST_PERMISSION);
assertNotNull(retrievedPermission);
assertEquals(TEST_PERMISSION, retrievedPermission.getName());
// Update permission
permission.setDescription("Updated Permission Description");
Permission updatedPermission = permissionManagerClient.updatePermission(TEST_DOMAIN, TEST_PERMISSION, permission);
assertNotNull(updatedPermission);
assertEquals("Updated Permission Description", updatedPermission.getDescription());
// Delete permission
assertDoesNotThrow(() -> permissionManagerClient.deletePermission(TEST_DOMAIN, TEST_PERMISSION));
// Clean up
permissionManagerClient.deleteDomain(TEST_DOMAIN);
}
@Test
@DisplayName("should manage roles successfully")
void roleManagement() {
// First create a domain
Domain domain = new Domain();
domain.setName(TEST_DOMAIN);
domain.setDescription("Test Domain Description");
permissionManagerClient.createDomain(domain);
// Create role
Role role = new Role();
role.setName(TEST_ROLE);
role.setDescription("Test Role Description");
role.setPermissions(List.of());
Role createdRole = permissionManagerClient.createRole(TEST_DOMAIN, role);
assertNotNull(createdRole);
assertEquals(TEST_ROLE, createdRole.getName());
// Get role
Role retrievedRole = permissionManagerClient.getRole(TEST_DOMAIN, TEST_ROLE);
assertNotNull(retrievedRole);
assertEquals(TEST_ROLE, retrievedRole.getName());
// Update role
role.setDescription("Updated Role Description");
Role updatedRole = permissionManagerClient.updateRole(TEST_DOMAIN, TEST_ROLE, role);
assertNotNull(updatedRole);
assertEquals("Updated Role Description", updatedRole.getDescription());
// Delete role
assertDoesNotThrow(() -> permissionManagerClient.deleteRole(TEST_DOMAIN, TEST_ROLE));
// Clean up
permissionManagerClient.deleteDomain(TEST_DOMAIN);
}
@Test
@DisplayName("should manage access successfully")
void accessManagement() {
// Setup: Create domain, permission, and role
Domain domain = new Domain();
domain.setName(TEST_DOMAIN);
permissionManagerClient.createDomain(domain);
Permission permission = new Permission();
permission.setName(TEST_PERMISSION);
permissionManagerClient.createPermission(TEST_DOMAIN, permission);
Role role = new Role();
role.setName(TEST_ROLE);
role.setPermissions(List.of());
permissionManagerClient.createRole(TEST_DOMAIN, role);
// Test permit access
PermitRequest permitRequest = new PermitRequest();
Permit permit = new Permit();
permit.setDomain(TEST_DOMAIN);
permit.setRoles(List.of(TEST_ROLE));
permit.setPermissions(List.of(TEST_PERMISSION));
permitRequest.setPermits(List.of(permit));
permitRequest.setUserId(TEST_USER);
permitRequest.setScope(TEST_SCOPE);
assertDoesNotThrow(() -> permissionManagerClient.permitAccess(permitRequest));
// Test check access
CheckAccessRequest checkRequest = new CheckAccessRequest();
checkRequest.setDomain(TEST_DOMAIN);
checkRequest.setPermission(TEST_PERMISSION);
checkRequest.setUserId(TEST_USER);
checkRequest.setScope(TEST_SCOPE);
PermittedResponse response = permissionManagerClient.checkAccess(checkRequest);
assertNotNull(response);
assertTrue(response.isPermitted());
// Test search permits
SearchPermitRequest searchRequest = new SearchPermitRequest();
searchRequest.setUserId(TEST_USER);
searchRequest.setScope(TEST_SCOPE);
List<Permission> permits = permissionManagerClient.searchPermits(searchRequest);
assertNotNull(permits);
assertFalse(permits.isEmpty());
// Test revoke access
assertDoesNotThrow(() -> permissionManagerClient.revokeAccess(permitRequest));
// Test revoke scope access
RevokeScopeAccessRequest revokeScopeRequest = new RevokeScopeAccessRequest();
revokeScopeRequest.setScope(TEST_SCOPE);
assertDoesNotThrow(() -> permissionManagerClient.revokeScopeAccess(revokeScopeRequest));
// Test revoke user access
RevokeUserAccessRequest revokeUserRequest = new RevokeUserAccessRequest();
revokeUserRequest.setUserId(TEST_USER);
assertDoesNotThrow(() -> permissionManagerClient.revokeUserAccess(revokeUserRequest));
// Clean up
permissionManagerClient.deleteDomain(TEST_DOMAIN);
}
}

View File

@ -0,0 +1,20 @@
package de.mummeit.pmg.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class TestSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
return http.build();
}
}

View File

@ -0,0 +1,19 @@
package de.mummeit.pmg.api.controller;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootConfiguration
@EnableAutoConfiguration
@EnableWebMvc
@EnableAspectJAutoProxy
@ComponentScan(basePackages = {
"de.mummeit.pmg.api.controller",
"de.mummeit.pmg.api.aspect"
})
public class TestConfig implements WebMvcConfigurer {
}

View File

@ -0,0 +1,65 @@
package de.mummeit.pmg.api.controller;
import de.mummeit.pmg.api.annotation.RequiresPermission;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/test")
public class TestController {
@GetMapping("/resource/{resourceId}")
@RequiresPermission(
domain = "test-domain",
permission = "read",
scope = "#resourceId",
userIdExpression = "#userId"
)
public String getResource(@PathVariable String resourceId, @RequestParam String userId) {
return "Access granted to resource " + resourceId + " for user " + userId;
}
@PostMapping("/resource/{resourceId}/action")
@RequiresPermission(
domain = "test-domain",
permission = "write",
scope = "#resourceId",
userIdExpression = "#request.userId"
)
public String performAction(
@PathVariable String resourceId,
@RequestBody ActionRequest request
) {
return "Action performed on resource " + resourceId + " by user " + request.getUserId();
}
@GetMapping("/secured-resource/{resourceId}")
@RequiresPermission(
domain = "test-domain",
permission = "read",
scope = "#resourceId"
)
public String getSecuredResource(@PathVariable String resourceId) {
return "Access granted to secured resource " + resourceId;
}
public static class ActionRequest {
private String userId;
private String action;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
}

View File

@ -0,0 +1,168 @@
package de.mummeit.pmg.api.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.mummeit.pmg.api.PermissionManagerClient;
import de.mummeit.pmg.api.config.TestSecurityConfig;
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.service.SecurityService;
import de.mummeit.pmg.service.exception.AccessDeniedException;
import de.mummeit.utility.BaseIntegrationTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@AutoConfigureMockMvc
@ComponentScan(basePackages = {
"de.mummeit.pmg.api.controller",
"de.mummeit.pmg.api.aspect",
"de.mummeit.pmg.api.service"
})
@Import(TestSecurityConfig.class)
class TestControllerIntegrationTest extends BaseIntegrationTest {
@TestConfiguration
@EnableWebMvc
static class TestConfig {
@Bean
public TestController testController() {
return new TestController();
}
@Bean
public TestExceptionHandler testExceptionHandler() {
return new TestExceptionHandler();
}
@Bean
public SecurityService securityService() {
return new SecurityService();
}
}
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@MockBean
private PermissionManagerClient permissionManagerClient;
@Test
void getResource_whenPermitted_shouldSucceed() throws Exception {
// Given
PermittedResponse response = new PermittedResponse();
response.setPermitted(true);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
// When/Then
mockMvc.perform(get("/api/test/resource/123")
.param("userId", "user1"))
.andExpect(status().isOk())
.andExpect(content().string("Access granted to resource 123 for user user1"));
}
@Test
void getResource_whenDenied_shouldFail() throws Exception {
// Given
PermittedResponse response = new PermittedResponse();
response.setPermitted(false);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
// When/Then
mockMvc.perform(get("/api/test/resource/123")
.param("userId", "user1"))
.andExpect(status().isForbidden())
.andExpect(result -> result.getResolvedException().getClass().equals(AccessDeniedException.class));
}
@Test
void performAction_whenPermitted_shouldSucceed() throws Exception {
// Given
PermittedResponse response = new PermittedResponse();
response.setPermitted(true);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
TestController.ActionRequest request = new TestController.ActionRequest();
request.setUserId("user1");
request.setAction("test");
// When/Then
mockMvc.perform(post("/api/test/resource/123/action")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(content().string("Action performed on resource 123 by user user1"));
}
@Test
void performAction_whenDenied_shouldFail() throws Exception {
// Given
PermittedResponse response = new PermittedResponse();
response.setPermitted(false);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
TestController.ActionRequest request = new TestController.ActionRequest();
request.setUserId("user1");
request.setAction("test");
// When/Then
mockMvc.perform(post("/api/test/resource/123/action")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isForbidden())
.andExpect(result -> result.getResolvedException().getClass().equals(AccessDeniedException.class));
}
@Test
void getSecuredResource_whenPermitted_shouldSucceed() throws Exception {
// Given
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("user1", null)
);
PermittedResponse response = new PermittedResponse();
response.setPermitted(true);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
// When/Then
mockMvc.perform(get("/api/test/secured-resource/123"))
.andExpect(status().isOk())
.andExpect(content().string("Access granted to secured resource 123"));
}
@Test
void getSecuredResource_whenDenied_shouldFail() throws Exception {
// Given
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("user1", null)
);
PermittedResponse response = new PermittedResponse();
response.setPermitted(false);
when(permissionManagerClient.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
// When/Then
mockMvc.perform(get("/api/test/secured-resource/123"))
.andExpect(status().isForbidden())
.andExpect(result -> result.getResolvedException().getClass().equals(AccessDeniedException.class));
}
}

View File

@ -0,0 +1,17 @@
package de.mummeit.pmg.api.controller;
import de.mummeit.pmg.service.exception.AccessDeniedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
@ControllerAdvice(basePackageClasses = TestController.class)
public class TestExceptionHandler {
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleAccessDeniedException() {
// No response body needed, just return 403 status
}
}

View File

@ -0,0 +1,17 @@
package de.mummeit.pmg.api.service;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
@Service
public class SecurityService {
public String getCurrentUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
throw new IllegalStateException("No authenticated user found");
}
return authentication.getName();
}
}

View File

@ -0,0 +1,260 @@
package de.mummeit.pmg.service;
import de.mummeit.pmg.api.PermissionManagerClient;
import de.mummeit.pmg.api.model.access.request.*;
import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.structure.Permission;
import de.mummeit.pmg.service.exception.AccessDeniedException;
import de.mummeit.pmg.service.exception.IntegrationFailedException;
import de.mummeit.pmg.service.exception.InvalidPermissionRequestException;
import feign.FeignException;
import feign.Request;
import feign.RequestTemplate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PermissionManagerTest {
@Mock
private PermissionManagerClient client;
private PermissionManager permissionManager;
private static final String TEST_USER = "test-user";
private static final String TEST_DOMAIN = "test-domain";
private static final String TEST_PERMISSION = "test-permission";
private static final String TEST_ROLE = "test-role";
private static final String TEST_SCOPE = "test-scope";
@BeforeEach
void setUp() {
permissionManager = new PermissionManager(client);
}
@Test
@DisplayName("hasAccess should return true when user has permission")
void hasAccessShouldReturnTrueWhenUserHasPermission() {
PermittedResponse response = new PermittedResponse();
response.setPermitted(true);
when(client.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
boolean hasAccess = permissionManager.hasAccess(TEST_USER, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE);
assertTrue(hasAccess);
verify(client).checkAccess(any(CheckAccessRequest.class));
}
@Test
@DisplayName("hasAccess should throw AccessDeniedException when client throws FeignException")
void hasAccessShouldThrowAccessDeniedExceptionWhenClientThrowsFeignException() {
FeignException feignException = new FeignException.NotFound("Not Found", createRequest(), null, null);
when(client.checkAccess(any(CheckAccessRequest.class))).thenThrow(feignException);
assertThrows(AccessDeniedException.class, () ->
permissionManager.hasAccess(TEST_USER, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE)
);
}
@Test
@DisplayName("hasAccess should throw InvalidPermissionRequestException when parameters are invalid")
void hasAccessShouldThrowInvalidPermissionRequestExceptionWhenParametersAreInvalid() {
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.hasAccess(null, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.hasAccess("", TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.hasAccess(TEST_USER, null, TEST_PERMISSION, TEST_SCOPE)
);
}
@Test
@DisplayName("grantAccess should throw InvalidPermissionRequestException when permissions and roles are empty")
void grantAccessShouldThrowInvalidPermissionRequestExceptionWhenPermissionsAndRolesAreEmpty() {
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.grantAccess(TEST_USER, TEST_DOMAIN, Collections.emptyList(), Collections.emptyList(), TEST_SCOPE)
);
}
@Test
@DisplayName("performIntegration should throw IntegrationFailedException when client throws exception")
void performIntegrationShouldThrowIntegrationFailedExceptionWhenClientThrowsException() {
List<Integration<?>> integrations = Collections.singletonList(mock(Integration.class));
FeignException feignException = new FeignException.InternalServerError("Internal Server Error", createRequest(), null, null);
doThrow(feignException).when(client).performIntegration(any());
IntegrationFailedException exception = assertThrows(IntegrationFailedException.class, () ->
permissionManager.performIntegration(integrations)
);
assertEquals(integrations, exception.getFailedIntegrations());
}
@Test
@DisplayName("grantMultiDomainAccess should validate permits")
void grantMultiDomainAccessShouldValidatePermits() {
Permit invalidPermit = new Permit();
invalidPermit.setDomain(""); // Invalid domain
List<Permit> permits = Collections.singletonList(invalidPermit);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.grantMultiDomainAccess(TEST_USER, permits, TEST_SCOPE)
);
}
@Test
@DisplayName("revokeMultiDomainAccess should validate permits")
void revokeMultiDomainAccessShouldValidatePermits() {
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.revokeMultiDomainAccess(TEST_USER, null, TEST_SCOPE)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.revokeMultiDomainAccess(TEST_USER, Collections.emptyList(), TEST_SCOPE)
);
}
@Test
@DisplayName("grantAccess should call client with correct request")
void grantAccessShouldCallClientWithCorrectRequest() {
List<String> permissions = Collections.singletonList(TEST_PERMISSION);
List<String> roles = Collections.singletonList(TEST_ROLE);
permissionManager.grantAccess(TEST_USER, TEST_DOMAIN, permissions, roles, TEST_SCOPE);
verify(client).permitAccess(argThat(request -> {
Permit permit = request.getPermits().get(0);
return TEST_USER.equals(request.getUserId()) &&
TEST_SCOPE.equals(request.getScope()) &&
TEST_DOMAIN.equals(permit.getDomain()) &&
permit.getPermissions().contains(TEST_PERMISSION) &&
permit.getRoles().contains(TEST_ROLE);
}));
}
@Test
@DisplayName("revokeAccess should call client with correct request")
void revokeAccessShouldCallClientWithCorrectRequest() {
List<String> permissions = Collections.singletonList(TEST_PERMISSION);
List<String> roles = Collections.singletonList(TEST_ROLE);
permissionManager.revokeAccess(TEST_USER, TEST_DOMAIN, permissions, roles, TEST_SCOPE);
verify(client).revokeAccess(argThat(request -> {
Permit permit = request.getPermits().get(0);
return TEST_USER.equals(request.getUserId()) &&
TEST_SCOPE.equals(request.getScope()) &&
TEST_DOMAIN.equals(permit.getDomain()) &&
permit.getPermissions().contains(TEST_PERMISSION) &&
permit.getRoles().contains(TEST_ROLE);
}));
}
@Test
@DisplayName("revokeAllUserAccess should call client with correct request")
void revokeAllUserAccessShouldCallClientWithCorrectRequest() {
permissionManager.revokeAllUserAccess(TEST_USER);
verify(client).revokeUserAccess(argThat(request ->
TEST_USER.equals(request.getUserId())
));
}
@Test
@DisplayName("revokeAllScopeAccess should call client with correct request")
void revokeAllScopeAccessShouldCallClientWithCorrectRequest() {
permissionManager.revokeAllScopeAccess(TEST_SCOPE);
verify(client).revokeScopeAccess(argThat(request ->
TEST_SCOPE.equals(request.getScope())
));
}
@Test
@DisplayName("findUserPermissions should return permissions from client")
void findUserPermissionsShouldReturnPermissionsFromClient() {
Permission permission = new Permission();
permission.setName(TEST_PERMISSION);
List<Permission> expectedPermissions = Collections.singletonList(permission);
when(client.searchPermits(any(SearchPermitRequest.class))).thenReturn(expectedPermissions);
List<Permission> actualPermissions = permissionManager.findUserPermissions(TEST_USER, TEST_SCOPE);
assertEquals(expectedPermissions, actualPermissions);
verify(client).searchPermits(argThat(request ->
TEST_USER.equals(request.getUserId()) &&
TEST_SCOPE.equals(request.getScope())
));
}
@Test
@DisplayName("grantMultiDomainAccess should call client with correct request")
void grantMultiDomainAccessShouldCallClientWithCorrectRequest() {
Permit permit1 = new Permit();
permit1.setDomain(TEST_DOMAIN);
permit1.setPermissions(Collections.singletonList(TEST_PERMISSION));
permit1.setRoles(Collections.singletonList(TEST_ROLE));
Permit permit2 = new Permit();
permit2.setDomain(TEST_DOMAIN + "-2");
permit2.setPermissions(Collections.singletonList(TEST_PERMISSION + "-2"));
permit2.setRoles(Collections.singletonList(TEST_ROLE + "-2"));
List<Permit> permits = Arrays.asList(permit1, permit2);
permissionManager.grantMultiDomainAccess(TEST_USER, permits, TEST_SCOPE);
verify(client).permitAccess(argThat(request ->
TEST_USER.equals(request.getUserId()) &&
TEST_SCOPE.equals(request.getScope()) &&
request.getPermits().equals(permits)
));
}
@Test
@DisplayName("revokeMultiDomainAccess should call client with correct request")
void revokeMultiDomainAccessShouldCallClientWithCorrectRequest() {
Permit permit1 = new Permit();
permit1.setDomain(TEST_DOMAIN);
permit1.setPermissions(Collections.singletonList(TEST_PERMISSION));
permit1.setRoles(Collections.singletonList(TEST_ROLE));
Permit permit2 = new Permit();
permit2.setDomain(TEST_DOMAIN + "-2");
permit2.setPermissions(Collections.singletonList(TEST_PERMISSION + "-2"));
permit2.setRoles(Collections.singletonList(TEST_ROLE + "-2"));
List<Permit> permits = Arrays.asList(permit1, permit2);
permissionManager.revokeMultiDomainAccess(TEST_USER, permits, TEST_SCOPE);
verify(client).revokeAccess(argThat(request ->
TEST_USER.equals(request.getUserId()) &&
TEST_SCOPE.equals(request.getScope()) &&
request.getPermits().equals(permits)
));
}
private Request createRequest() {
return Request.create(Request.HttpMethod.GET, "url", new HashMap<>(), null, new RequestTemplate());
}
}

View File

@ -0,0 +1,286 @@
package de.mummeit.pmg.service.builder;
import de.mummeit.pmg.api.model.integration.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class IntegrationBuilderTest {
private static final String TEST_DOMAIN = "test-domain";
private static final String TEST_DESCRIPTION = "test-description";
private static final String TEST_PERMISSION = "test-permission";
private static final String TEST_ROLE = "test-role";
@Test
@DisplayName("should build domain integrations")
void shouldBuildDomainIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof DomainIntegration);
DomainIntegration domainIntegration = (DomainIntegration) integration;
assertEquals(Integration.Action.create, domainIntegration.getAction());
assertEquals(TEST_DOMAIN, domainIntegration.getData().getName());
assertEquals(TEST_DESCRIPTION, domainIntegration.getData().getDescription());
assertEquals("domain:" + TEST_DOMAIN + ":create", domainIntegration.getId());
}
@Test
@DisplayName("should update domain integrations")
void shouldUpdateDomainIntegrations() {
String newName = "new-domain";
String newDescription = "Updated Description";
List<Integration<?>> integrations = IntegrationBuilder.create()
.updateDomain(TEST_DOMAIN, newName, newDescription)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
assertEquals(2, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof DomainIntegration);
DomainIntegration domainIntegration = (DomainIntegration) integration;
assertEquals(Integration.Action.update, domainIntegration.getAction());
assertEquals(newName, domainIntegration.getData().getName());
assertEquals(TEST_DOMAIN, domainIntegration.getData().getOldName());
assertEquals(newDescription, domainIntegration.getData().getDescription());
assertEquals("domain:" + TEST_DOMAIN + ":update", domainIntegration.getId());
PermissionIntegration permissionIntegration = (PermissionIntegration) integrations.get(1);
assertEquals(newName, permissionIntegration.getData().getDomain());
}
@Test
@DisplayName("should delete domain integrations")
void shouldDeleteDomainIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.deleteDomain(TEST_DOMAIN)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof DomainIntegration);
DomainIntegration domainIntegration = (DomainIntegration) integration;
assertEquals(Integration.Action.delete, domainIntegration.getAction());
assertEquals(TEST_DOMAIN, domainIntegration.getData().getName());
assertEquals("domain:" + TEST_DOMAIN + ":delete", domainIntegration.getId());
}
@Test
@DisplayName("should build permission integrations")
void shouldBuildPermissionIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
assertEquals(2, integrations.size());
Integration<?> integration = integrations.get(1);
assertTrue(integration instanceof PermissionIntegration);
PermissionIntegration permissionIntegration = (PermissionIntegration) integration;
assertEquals(Integration.Action.create, permissionIntegration.getAction());
assertEquals(TEST_DOMAIN, permissionIntegration.getData().getDomain());
assertEquals(TEST_PERMISSION, permissionIntegration.getData().getName());
assertEquals(TEST_DESCRIPTION, permissionIntegration.getData().getDescription());
assertEquals("permission:" + TEST_DOMAIN + ":" + TEST_PERMISSION + ":create", permissionIntegration.getId());
}
@Test
@DisplayName("should update permission integrations")
void shouldUpdatePermissionIntegrations() {
String newName = "new-permission";
String newDescription = "Updated Permission";
List<Integration<?>> integrations = IntegrationBuilder.create()
.selectDomain(TEST_DOMAIN)
.updatePermission(TEST_PERMISSION, newName, newDescription)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof PermissionIntegration);
PermissionIntegration permissionIntegration = (PermissionIntegration) integration;
assertEquals(Integration.Action.update, permissionIntegration.getAction());
assertEquals(TEST_DOMAIN, permissionIntegration.getData().getDomain());
assertEquals(newName, permissionIntegration.getData().getName());
assertEquals(TEST_PERMISSION, permissionIntegration.getData().getOldName());
assertEquals(newDescription, permissionIntegration.getData().getDescription());
assertEquals("permission:" + TEST_DOMAIN + ":" + TEST_PERMISSION + ":update", permissionIntegration.getId());
}
@Test
@DisplayName("should remove permission integrations")
void shouldRemovePermissionIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.selectDomain(TEST_DOMAIN)
.removePermission(TEST_PERMISSION)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof PermissionIntegration);
PermissionIntegration permissionIntegration = (PermissionIntegration) integration;
assertEquals(Integration.Action.delete, permissionIntegration.getAction());
assertEquals(TEST_DOMAIN, permissionIntegration.getData().getDomain());
assertEquals(TEST_PERMISSION, permissionIntegration.getData().getName());
assertEquals("permission:" + TEST_DOMAIN + ":" + TEST_PERMISSION + ":delete", permissionIntegration.getId());
}
@Test
@DisplayName("should build role integrations")
void shouldBuildRoleIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addRole(TEST_ROLE, TEST_DESCRIPTION)
.build();
assertEquals(2, integrations.size());
Integration<?> integration = integrations.get(1);
assertTrue(integration instanceof RoleIntegration);
RoleIntegration roleIntegration = (RoleIntegration) integration;
assertEquals(Integration.Action.create, roleIntegration.getAction());
assertEquals(TEST_DOMAIN, roleIntegration.getData().getDomain());
assertEquals(TEST_ROLE, roleIntegration.getData().getName());
assertEquals(TEST_DESCRIPTION, roleIntegration.getData().getDescription());
assertEquals("role:" + TEST_DOMAIN + ":" + TEST_ROLE + ":create", roleIntegration.getId());
}
@Test
@DisplayName("should update role integrations")
void shouldUpdateRoleIntegrations() {
String newName = "new-role";
String newDescription = "Updated Role";
List<Integration<?>> integrations = IntegrationBuilder.create()
.selectDomain(TEST_DOMAIN)
.updateRole(TEST_ROLE, newName, newDescription)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof RoleIntegration);
RoleIntegration roleIntegration = (RoleIntegration) integration;
assertEquals(Integration.Action.update, roleIntegration.getAction());
assertEquals(TEST_DOMAIN, roleIntegration.getData().getDomain());
assertEquals(newName, roleIntegration.getData().getName());
assertEquals(TEST_ROLE, roleIntegration.getData().getOldName());
assertEquals(newDescription, roleIntegration.getData().getDescription());
assertEquals("role:" + TEST_DOMAIN + ":" + TEST_ROLE + ":update", roleIntegration.getId());
}
@Test
@DisplayName("should remove role integrations")
void shouldRemoveRoleIntegrations() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.selectDomain(TEST_DOMAIN)
.removeRole(TEST_ROLE)
.build();
assertEquals(1, integrations.size());
Integration<?> integration = integrations.get(0);
assertTrue(integration instanceof RoleIntegration);
RoleIntegration roleIntegration = (RoleIntegration) integration;
assertEquals(Integration.Action.delete, roleIntegration.getAction());
assertEquals(TEST_DOMAIN, roleIntegration.getData().getDomain());
assertEquals(TEST_ROLE, roleIntegration.getData().getName());
assertEquals("role:" + TEST_DOMAIN + ":" + TEST_ROLE + ":delete", roleIntegration.getId());
}
@Test
@DisplayName("should build role permission relation integrations")
void shouldBuildRolePermissionRelationIntegrations() {
List<String> permissions = Arrays.asList(TEST_PERMISSION, TEST_PERMISSION + "-2");
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.assignPermissionsToRole(TEST_ROLE, permissions)
.build();
assertEquals(2, integrations.size());
Integration<?> integration = integrations.get(1);
assertTrue(integration instanceof RolePermissionRelationIntegration);
RolePermissionRelationIntegration relationIntegration = (RolePermissionRelationIntegration) integration;
assertEquals(Integration.Action.create, relationIntegration.getAction());
assertEquals(TEST_DOMAIN, relationIntegration.getData().getDomain());
assertEquals(TEST_ROLE, relationIntegration.getData().getRole());
assertEquals(permissions, relationIntegration.getData().getPermissions());
assertEquals("role-permissions:" + TEST_DOMAIN + ":" + TEST_ROLE + ":" + String.join(",", permissions) + ":create", relationIntegration.getId());
}
@Test
@DisplayName("should build multiple integrations in sequence")
void shouldBuildMultipleIntegrationsInSequence() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.addRole(TEST_ROLE, TEST_DESCRIPTION)
.assignPermissionsToRole(TEST_ROLE, Arrays.asList(TEST_PERMISSION))
.build();
assertEquals(4, integrations.size());
assertTrue(integrations.get(0) instanceof DomainIntegration);
assertTrue(integrations.get(1) instanceof PermissionIntegration);
assertTrue(integrations.get(2) instanceof RoleIntegration);
assertTrue(integrations.get(3) instanceof RolePermissionRelationIntegration);
}
@Test
@DisplayName("should throw exception when no domain context is set")
void shouldThrowExceptionWhenNoDomainContextIsSet() {
assertThrows(IllegalStateException.class, () ->
IntegrationBuilder.create().addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
);
}
@Test
@DisplayName("should allow switching domain context")
void shouldAllowSwitchingDomainContext() {
String secondDomain = "second-domain";
List<Integration<?>> integrations = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.createDomain(secondDomain, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
assertEquals(4, integrations.size());
PermissionIntegration firstPermission = (PermissionIntegration) integrations.get(1);
PermissionIntegration secondPermission = (PermissionIntegration) integrations.get(3);
assertEquals(TEST_DOMAIN, firstPermission.getData().getDomain());
assertEquals(secondDomain, secondPermission.getData().getDomain());
}
@Test
@DisplayName("should allow selecting domain without creating it")
void shouldAllowSelectingDomainWithoutCreatingIt() {
List<Integration<?>> integrations = IntegrationBuilder.create()
.selectDomain(TEST_DOMAIN)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
assertEquals(1, integrations.size());
PermissionIntegration permission = (PermissionIntegration) integrations.get(0);
assertEquals(TEST_DOMAIN, permission.getData().getDomain());
}
@Test
@DisplayName("should generate consistent IDs for same integrations")
void shouldGenerateConsistentIdsForSameIntegrations() {
List<Integration<?>> firstRun = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
List<Integration<?>> secondRun = IntegrationBuilder.create()
.createDomain(TEST_DOMAIN, TEST_DESCRIPTION)
.addPermission(TEST_PERMISSION, TEST_DESCRIPTION)
.build();
assertEquals(firstRun.get(0).getId(), secondRun.get(0).getId());
assertEquals(firstRun.get(1).getId(), secondRun.get(1).getId());
}
}

View File

@ -0,0 +1,155 @@
package de.mummeit.pmg.service.config;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.mummeit.pmg.api.model.integration.DomainIntegration;
import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.service.PermissionManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AbstractPermissionManagerConfigurationTest {
@Mock
private PermissionManager permissionManager;
@Mock
private ResourceLoader resourceLoader;
@Mock
private ObjectMapper objectMapper;
@Mock
private Resource resource;
@Mock
private ApplicationReadyEvent event;
private TestPermissionManagerConfiguration configuration;
@BeforeEach
void setUp() {
configuration = new TestPermissionManagerConfiguration(
permissionManager,
resourceLoader,
objectMapper
);
}
@Test
@DisplayName("should perform programmatic integrations on startup")
void shouldPerformProgrammaticIntegrationsOnStartup() {
// Given
List<Integration<?>> integrations = Collections.singletonList(
DomainIntegration.builder()
.id("1")
.action(Integration.Action.create)
.data(DomainIntegration.Data.builder()
.name("test")
.description("test")
.build())
.build()
);
configuration.setProgrammaticIntegrations(integrations);
// When
configuration.performIntegrationsOnStartup();
// Then
verify(permissionManager).performIntegration(integrations);
verify(resourceLoader, never()).getResource(any());
}
@Test
@DisplayName("should perform JSON integrations on startup")
void shouldPerformJsonIntegrationsOnStartup() throws IOException {
// Given
List<Integration<?>> integrations = Collections.singletonList(
DomainIntegration.builder()
.id("1")
.action(Integration.Action.create)
.data(DomainIntegration.Data.builder()
.name("test")
.description("test")
.build())
.build()
);
configuration.setProgrammaticIntegrations(null);
configuration.setJsonPath("classpath:integrations.json");
when(resourceLoader.getResource("classpath:integrations.json")).thenReturn(resource);
when(resource.getInputStream()).thenReturn(new ByteArrayInputStream("[]".getBytes()));
doReturn(integrations).when(objectMapper).readValue(any(InputStream.class), any(TypeReference.class));
// When
configuration.performIntegrationsOnStartup();
// Then
verify(permissionManager).performIntegration(integrations);
verify(resourceLoader).getResource("classpath:integrations.json");
}
@Test
@DisplayName("should handle no integrations gracefully")
void shouldHandleNoIntegrationsGracefully() {
// Given
configuration.setProgrammaticIntegrations(null);
configuration.setJsonPath(null);
// When
configuration.performIntegrationsOnStartup();
// Then
verify(permissionManager, never()).performIntegration(any());
verify(resourceLoader, never()).getResource(any());
}
private static class TestPermissionManagerConfiguration extends AbstractPermissionManagerConfiguration {
private List<Integration<?>> programmaticIntegrations;
private String jsonPath;
public TestPermissionManagerConfiguration(
PermissionManager permissionManager,
ResourceLoader resourceLoader,
ObjectMapper objectMapper
) {
super(permissionManager, resourceLoader, objectMapper);
}
public void setProgrammaticIntegrations(List<Integration<?>> integrations) {
this.programmaticIntegrations = integrations;
}
public void setJsonPath(String path) {
this.jsonPath = path;
}
@Override
protected List<Integration<?>> getIntegrations() {
return programmaticIntegrations;
}
@Override
protected String getIntegrationsJsonPath() {
return jsonPath;
}
}
}