Add functionality for all pmg features

This commit is contained in:
2025-03-25 01:37:58 +01:00
parent aa4de7cbe2
commit 91bc0b3b6f
24 changed files with 452 additions and 47 deletions

169
README.md
View File

@ -11,6 +11,7 @@ A Spring Boot SDK for integrating permission management into your Java applicati
- Scope-based access control - Scope-based access control
- Multi-domain permission management - Multi-domain permission management
- Flexible user ID resolution - Flexible user ID resolution
- API key authentication support
## Requirements ## Requirements
@ -54,13 +55,179 @@ Add the following properties to your Spring Boot application properties file (`a
```yaml ```yaml
permission-manager: permission-manager:
base-url: http://your-permission-manager-url # Required: URL of your Permission Manager instance url: http://your-permission-manager-url # Required: URL of your Permission Manager instance
auth:
enabled: false # Optional: Enable/disable API key authentication (defaults to false)
api-key: your-api-key # Required when auth.enabled is true
security: security:
enabled: true # Optional: Enable/disable security checks (defaults to true) enabled: true # Optional: Enable/disable security checks (defaults to true)
``` ```
These properties can be configured in any valid Spring Boot configuration source (application.yml, application.properties, environment variables, etc.) following Spring Boot's standard property resolution order. These properties can be configured in any valid Spring Boot configuration source (application.yml, application.properties, environment variables, etc.) following Spring Boot's standard property resolution order.
### 3. API Key Authentication
The SDK supports API key authentication for requests to the Permission Manager service. When enabled, the SDK will automatically add an `x-api-key` header to all requests.
To enable API key authentication:
1. Set `permission-manager.auth.enabled` to `true`
2. Provide your API key in `permission-manager.auth.api-key`
Example configuration:
```yaml
permission-manager:
auth:
enabled: true
api-key: your-secret-api-key-here
```
Note: When authentication is enabled, the API key is required. The SDK will throw an `IllegalStateException` if authentication is enabled but no API key is provided.
### 4. Integration Configuration
The SDK provides a flexible way to configure and perform integrations on application startup through the `AbstractPermissionManagerConfiguration` class. You can use this to automatically set up your permission structure (domains, permissions, roles, and their relationships) when your application starts.
There are two ways to provide integrations:
#### Option 1: JSON Configuration
Create a JSON file containing your integration configuration:
```json
[
{
"id": "domain-1",
"entity": "domain",
"action": "create",
"data": {
"name": "users",
"description": "User management domain"
}
},
{
"id": "permission-1",
"entity": "permission",
"action": "create",
"data": {
"domain": "users",
"name": "read",
"description": "Permission to read user data"
}
},
{
"id": "role-1",
"entity": "role",
"action": "create",
"data": {
"domain": "users",
"name": "user_viewer",
"description": "Role for viewing user data"
}
},
{
"id": "role-permission-1",
"entity": "role_permission_relation",
"action": "create",
"data": {
"domain": "users",
"role": "user_viewer",
"permissions": ["read"]
}
}
]
```
Then create a configuration class that extends `AbstractPermissionManagerConfiguration`:
```java
import de.mummeit.pmg.config.AbstractPermissionManagerConfiguration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PermissionManagerIntegrationConfig extends AbstractPermissionManagerConfiguration {
@Override
protected String getIntegrationsJsonPath() {
return "classpath:permission-integrations.json";
}
}
```
#### Option 2: Programmatic Configuration
Alternatively, you can build your integrations programmatically using the fluent `IntegrationBuilder`:
```java
import de.mummeit.pmg.builder.IntegrationBuilder;
import de.mummeit.pmg.config.AbstractPermissionManagerConfiguration;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PermissionManagerIntegrationConfig extends AbstractPermissionManagerConfiguration {
@Override
protected List<Integration<?>> getIntegrations() {
return IntegrationBuilder.create()
// Create a domain and set it as the current context
.createDomain("users", "User management domain")
// Add permissions to the current domain
.addPermission("read", "Permission to read user data")
.addPermission("write", "Permission to write user data")
// Add a role to the current domain
.addRole("user_viewer", "Role for viewing user data")
// Assign permissions to the role
.assignPermissionsToRole("user_viewer", List.of("read"))
// Create another domain with its permissions and roles
.createDomain("orders", "Order management domain")
.addPermission("view", "Permission to view orders")
.addPermission("create", "Permission to create orders")
.addRole("order_manager", "Role for managing orders")
.assignPermissionsToRole("order_manager", List.of("view", "create"))
.build();
}
}
```
The `IntegrationBuilder` provides a fluent API for creating integrations with features like:
- Domain context management (automatically tracks the current domain)
- Method chaining for building complex permission structures
- Type-safe operations for all integration types
- Automatic generation of integration IDs
Available builder methods:
- Domain operations:
- `createDomain(name, description)`
- `updateDomain(oldName, newName, description)`
- `deleteDomain(name)`
- `selectDomain(name)` - Switch context without creating a domain
- Permission operations:
- `addPermission(name, description)`
- `updatePermission(oldName, newName, description)`
- `removePermission(name)`
- Role operations:
- `addRole(name, description)`
- `updateRole(oldName, newName, description)`
- `removeRole(name)`
- Role-Permission relations:
- `assignPermissionsToRole(role, permissions)`
The builder ensures that operations are performed in the correct context by requiring a domain to be selected or created before performing domain-specific operations.
The integrations will be performed automatically when your application starts up. You can implement either `getIntegrations()` or `getIntegrationsJsonPath()` - if both are implemented, `getIntegrations()` takes precedence.
Available integration types:
- `DomainIntegration`: Create/update domains
- `PermissionIntegration`: Create/update permissions within domains
- `RoleIntegration`: Create/update roles within domains
- `RolePermissionRelationIntegration`: Manage relationships between roles and permissions
Each integration requires:
- `id`: A unique identifier for the integration
- `action`: The operation to perform (`create`, `update`, or `delete`)
- `data`: The entity-specific data for the integration
## Usage ## Usage
### 1. Annotation-Based Permission Checks ### 1. Annotation-Based Permission Checks

View File

@ -8,7 +8,7 @@
<groupId>de.mumme-it</groupId> <groupId>de.mumme-it</groupId>
<artifactId>permission-manager-sdk</artifactId> <artifactId>permission-manager-sdk</artifactId>
<version>0.1.1</version> <version>0.1.2</version>
<organization> <organization>
<name>Mumme-IT</name> <name>Mumme-IT</name>
<url>https://mumme-it.de</url> <url>https://mumme-it.de</url>

View File

@ -1,8 +1,10 @@
package de.mummeit.common.config; package de.mummeit.common.config;
import de.mummeit.pmg.config.PermissionManagerAuthConfiguration;
import feign.Client; import feign.Client;
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
@ -13,6 +15,7 @@ import org.springframework.context.annotation.PropertySource;
@ComponentScan(basePackages = "de.mummeit") @ComponentScan(basePackages = "de.mummeit")
@EnableFeignClients(basePackages = "de.mummeit") @EnableFeignClients(basePackages = "de.mummeit")
@PropertySource(value = "classpath:permission-manager-sdk-application.yaml") @PropertySource(value = "classpath:permission-manager-sdk-application.yaml")
@EnableConfigurationProperties(PermissionManagerAuthConfiguration.class)
public class PermissionManagerSdkConfiguration { public class PermissionManagerSdkConfiguration {
@Bean @Bean

View File

@ -5,7 +5,9 @@ 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.RevokeScopeAccessRequest;
import de.mummeit.pmg.api.model.access.request.RevokeUserAccessRequest; 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.request.SearchPermitRequest;
import de.mummeit.pmg.api.model.access.request.ListPermittedScopesRequest;
import de.mummeit.pmg.api.model.access.response.PermittedResponse; import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.access.response.ListPermittedScopesResponse;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.structure.Domain; import de.mummeit.pmg.api.model.structure.Domain;
import de.mummeit.pmg.api.model.structure.Permission; import de.mummeit.pmg.api.model.structure.Permission;
@ -43,6 +45,9 @@ public interface PermissionManagerClient {
@PatchMapping("/api/v1/access/revoke/user") @PatchMapping("/api/v1/access/revoke/user")
void revokeUserAccess(@RequestBody RevokeUserAccessRequest request); void revokeUserAccess(@RequestBody RevokeUserAccessRequest request);
@PostMapping("/api/v1/access/scopes")
ListPermittedScopesResponse listPermittedScopes(@RequestBody ListPermittedScopesRequest request);
// Domain Management // Domain Management
@PostMapping("/api/v1/domains") @PostMapping("/api/v1/domains")
Domain createDomain(@RequestBody Domain domain); Domain createDomain(@RequestBody Domain domain);

View File

@ -1,7 +1,5 @@
package de.mummeit.pmg.api.annotation; package de.mummeit.pmg.api.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**

View File

@ -3,7 +3,7 @@ package de.mummeit.pmg.api.aspect;
import de.mummeit.pmg.api.PermissionManagerClient; import de.mummeit.pmg.api.PermissionManagerClient;
import de.mummeit.pmg.api.annotation.RequiresPermission; import de.mummeit.pmg.api.annotation.RequiresPermission;
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest; import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
import de.mummeit.pmg.service.exception.AccessDeniedException; import de.mummeit.pmg.exception.AccessDeniedException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
@ -11,7 +11,6 @@ import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
@ -22,9 +21,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
@Aspect @Aspect

View File

@ -0,0 +1,10 @@
package de.mummeit.pmg.api.model.access.request;
import lombok.Data;
@Data
public class ListPermittedScopesRequest {
private String userId;
private String domain;
private String permission;
}

View File

@ -0,0 +1,10 @@
package de.mummeit.pmg.api.model.access.response;
import lombok.Data;
import java.util.List;
@Data
public class ListPermittedScopesResponse {
private List<String> scopes;
}

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.builder; package de.mummeit.pmg.builder;
import de.mummeit.pmg.api.model.integration.*; import de.mummeit.pmg.api.model.integration.*;

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.config; package de.mummeit.pmg.config;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -6,6 +6,8 @@ import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.service.PermissionManager; import de.mummeit.pmg.service.PermissionManager;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -24,8 +26,13 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public abstract class AbstractPermissionManagerConfiguration { public abstract class AbstractPermissionManagerConfiguration {
@Autowired
private final PermissionManager permissionManager; private final PermissionManager permissionManager;
@Autowired
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
@Autowired
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
/** /**

View File

@ -0,0 +1,34 @@
package de.mummeit.pmg.config;
import feign.RequestInterceptor;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
@Data
@Configuration
@ConfigurationProperties(prefix = "permission-manager")
public class PermissionManagerAuthConfiguration {
private Auth auth = new Auth();
@Data
public static class Auth {
private boolean enabled = false;
private String apiKey;
}
@Bean
public RequestInterceptor apiKeyInterceptor() {
return requestTemplate -> {
if (auth.isEnabled()) {
if (!StringUtils.hasText(auth.getApiKey())) {
throw new IllegalStateException("API key is required when authentication is enabled");
}
requestTemplate.header("x-api-key", auth.getApiKey());
}
};
}
}

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.exception; package de.mummeit.pmg.exception;
/** /**
* Exception thrown when access is denied for a user. * Exception thrown when access is denied for a user.

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.exception; package de.mummeit.pmg.exception;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.exception; package de.mummeit.pmg.exception;
/** /**
* Exception thrown when a permission request is invalid. * Exception thrown when a permission request is invalid.

View File

@ -1,4 +1,4 @@
package de.mummeit.pmg.service.exception; package de.mummeit.pmg.exception;
/** /**
* Base exception class for all Permission Manager related exceptions. * Base exception class for all Permission Manager related exceptions.

View File

@ -3,11 +3,11 @@ package de.mummeit.pmg.service;
import de.mummeit.pmg.api.PermissionManagerClient; import de.mummeit.pmg.api.PermissionManagerClient;
import de.mummeit.pmg.api.model.access.request.*; import de.mummeit.pmg.api.model.access.request.*;
import de.mummeit.pmg.api.model.access.response.PermittedResponse; import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.access.response.ListPermittedScopesResponse;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.structure.Permission; import de.mummeit.pmg.api.model.structure.Permission;
import de.mummeit.pmg.service.exception.AccessDeniedException; import de.mummeit.pmg.exception.IntegrationFailedException;
import de.mummeit.pmg.service.exception.IntegrationFailedException; import de.mummeit.pmg.exception.InvalidPermissionRequestException;
import de.mummeit.pmg.service.exception.InvalidPermissionRequestException;
import feign.FeignException; import feign.FeignException;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -31,7 +31,6 @@ public class PermissionManager {
* @param scope The scope * @param scope The scope
* @return true if the user has access, false otherwise * @return true if the user has access, false otherwise
* @throws InvalidPermissionRequestException if any of the required parameters are null or empty * @throws InvalidPermissionRequestException if any of the required parameters are null or empty
* @throws AccessDeniedException if the access check fails
*/ */
public boolean hasAccess(String userId, String domain, String permission, String scope) { public boolean hasAccess(String userId, String domain, String permission, String scope) {
validateParameters(userId, domain, permission, scope); validateParameters(userId, domain, permission, scope);
@ -46,12 +45,9 @@ public class PermissionManager {
PermittedResponse response = client.checkAccess(request); PermittedResponse response = client.checkAccess(request);
return response.isPermitted(); return response.isPermitted();
} catch (FeignException e) { } catch (FeignException e) {
throw new AccessDeniedException( throw new InvalidPermissionRequestException(
"Failed to check access", "Technical error while checking access permissions",
userId, "Failed to communicate with permission service: " + e.getMessage(),
domain,
permission,
scope,
e e
); );
} }
@ -276,6 +272,35 @@ public class PermissionManager {
} }
} }
/**
* Lists all scopes where a user has access to a specific permission in a domain.
*
* @param userId The ID of the user
* @param domain The domain name
* @param permission The permission name
* @return List of scopes where the user has access
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
*/
public List<String> listPermittedScopes(String userId, String domain, String permission) {
validateParameters(userId, domain, permission);
try {
ListPermittedScopesRequest request = new ListPermittedScopesRequest();
request.setUserId(userId);
request.setDomain(domain);
request.setPermission(permission);
ListPermittedScopesResponse response = client.listPermittedScopes(request);
return response.getScopes();
} catch (FeignException e) {
throw new InvalidPermissionRequestException(
"Failed to list permitted scopes",
"Error occurred while listing permitted scopes",
e
);
}
}
private void validateParameters(String userId, String domain, String permission, String scope) { private void validateParameters(String userId, String domain, String permission, String scope) {
validateUserId(userId); validateUserId(userId);
validateDomain(domain); validateDomain(domain);

View File

@ -10,5 +10,9 @@ spring:
permission-manager: permission-manager:
connect-timeout: 1000 connect-timeout: 1000
read-timeout: 2000 read-timeout: 2000
permission-manager: permission-manager:
url: http://localhost:6060 url: http://localhost:6060
auth:
enabled: false
api-key:

View File

@ -1,14 +1,10 @@
package de.mummeit.pmg.api; package de.mummeit.pmg.api;
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest; import de.mummeit.pmg.api.model.access.request.*;
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.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.integration.DomainIntegration; import de.mummeit.pmg.api.model.access.response.ListPermittedScopesResponse;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.integration.DomainIntegration;
import de.mummeit.pmg.api.model.integration.PermissionIntegration; import de.mummeit.pmg.api.model.integration.PermissionIntegration;
import de.mummeit.pmg.api.model.integration.RoleIntegration; import de.mummeit.pmg.api.model.integration.RoleIntegration;
import de.mummeit.pmg.api.model.integration.RolePermissionRelationIntegration; import de.mummeit.pmg.api.model.integration.RolePermissionRelationIntegration;
@ -16,6 +12,7 @@ import de.mummeit.pmg.api.model.structure.Domain;
import de.mummeit.pmg.api.model.structure.Permission; import de.mummeit.pmg.api.model.structure.Permission;
import de.mummeit.pmg.api.model.structure.Role; import de.mummeit.pmg.api.model.structure.Role;
import de.mummeit.utility.BaseIntegrationTest; import de.mummeit.utility.BaseIntegrationTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -35,6 +32,15 @@ class PermissionManagerClientIntegrationTest extends BaseIntegrationTest {
private static final String TEST_USER = "test-user"; private static final String TEST_USER = "test-user";
private static final String TEST_SCOPE = "test-scope"; private static final String TEST_SCOPE = "test-scope";
@AfterEach
void cleanup() {
try {
permissionManagerClient.deleteDomain(TEST_DOMAIN);
} catch (Exception e) {
// Ignore errors during cleanup
}
}
@Test @Test
@DisplayName("should return health status") @DisplayName("should return health status")
void getHealthStatus() { void getHealthStatus() {
@ -251,4 +257,62 @@ class PermissionManagerClientIntegrationTest extends BaseIntegrationTest {
// Clean up // Clean up
permissionManagerClient.deleteDomain(TEST_DOMAIN); permissionManagerClient.deleteDomain(TEST_DOMAIN);
} }
@Test
@DisplayName("should list permitted scopes successfully")
void listPermittedScopes() {
// 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);
// Grant access in multiple scopes
PermitRequest permitRequest1 = new PermitRequest();
Permit permit1 = new Permit();
permit1.setDomain(TEST_DOMAIN);
permit1.setRoles(List.of(TEST_ROLE));
permit1.setPermissions(List.of(TEST_PERMISSION));
permitRequest1.setPermits(List.of(permit1));
permitRequest1.setUserId(TEST_USER);
permitRequest1.setScope(TEST_SCOPE);
permissionManagerClient.permitAccess(permitRequest1);
String secondScope = TEST_SCOPE + "-2";
PermitRequest permitRequest2 = new PermitRequest();
Permit permit2 = new Permit();
permit2.setDomain(TEST_DOMAIN);
permit2.setRoles(List.of(TEST_ROLE));
permit2.setPermissions(List.of(TEST_PERMISSION));
permitRequest2.setPermits(List.of(permit2));
permitRequest2.setUserId(TEST_USER);
permitRequest2.setScope(secondScope);
permissionManagerClient.permitAccess(permitRequest2);
// Test listing permitted scopes
ListPermittedScopesRequest request = new ListPermittedScopesRequest();
request.setUserId(TEST_USER);
request.setDomain(TEST_DOMAIN);
request.setPermission(TEST_PERMISSION);
ListPermittedScopesResponse response = permissionManagerClient.listPermittedScopes(request);
assertNotNull(response);
assertNotNull(response.getScopes());
assertEquals(2, response.getScopes().size());
assertTrue(response.getScopes().contains(TEST_SCOPE));
assertTrue(response.getScopes().contains(secondScope));
// Clean up
permissionManagerClient.deleteRole(TEST_DOMAIN, TEST_ROLE);
permissionManagerClient.deletePermission(TEST_DOMAIN, TEST_PERMISSION);
permissionManagerClient.deleteDomain(TEST_DOMAIN);
}
} }

View File

@ -6,7 +6,7 @@ import de.mummeit.pmg.api.config.TestSecurityConfig;
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest; import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
import de.mummeit.pmg.api.model.access.response.PermittedResponse; import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.service.SecurityService; import de.mummeit.pmg.api.service.SecurityService;
import de.mummeit.pmg.service.exception.AccessDeniedException; import de.mummeit.pmg.exception.AccessDeniedException;
import de.mummeit.utility.BaseIntegrationTest; import de.mummeit.utility.BaseIntegrationTest;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View File

@ -1,6 +1,6 @@
package de.mummeit.pmg.api.controller; package de.mummeit.pmg.api.controller;
import de.mummeit.pmg.service.exception.AccessDeniedException; import de.mummeit.pmg.exception.AccessDeniedException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;

View File

@ -3,11 +3,12 @@ package de.mummeit.pmg.service;
import de.mummeit.pmg.api.PermissionManagerClient; import de.mummeit.pmg.api.PermissionManagerClient;
import de.mummeit.pmg.api.model.access.request.*; import de.mummeit.pmg.api.model.access.request.*;
import de.mummeit.pmg.api.model.access.response.PermittedResponse; import de.mummeit.pmg.api.model.access.response.PermittedResponse;
import de.mummeit.pmg.api.model.access.response.ListPermittedScopesResponse;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.api.model.structure.Permission; import de.mummeit.pmg.api.model.structure.Permission;
import de.mummeit.pmg.service.exception.AccessDeniedException; import de.mummeit.pmg.exception.AccessDeniedException;
import de.mummeit.pmg.service.exception.IntegrationFailedException; import de.mummeit.pmg.exception.IntegrationFailedException;
import de.mummeit.pmg.service.exception.InvalidPermissionRequestException; import de.mummeit.pmg.exception.InvalidPermissionRequestException;
import feign.FeignException; import feign.FeignException;
import feign.Request; import feign.Request;
import feign.RequestTemplate; import feign.RequestTemplate;
@ -25,6 +26,7 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -60,12 +62,25 @@ class PermissionManagerTest {
} }
@Test @Test
@DisplayName("hasAccess should throw AccessDeniedException when client throws FeignException") @DisplayName("hasAccess should return false when access is denied")
void hasAccessShouldThrowAccessDeniedExceptionWhenClientThrowsFeignException() { void hasAccessShouldReturnFalseWhenAccessIsDenied() {
PermittedResponse response = new PermittedResponse();
response.setPermitted(false);
when(client.checkAccess(any(CheckAccessRequest.class))).thenReturn(response);
boolean hasAccess = permissionManager.hasAccess(TEST_USER, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE);
assertFalse(hasAccess);
verify(client).checkAccess(any(CheckAccessRequest.class));
}
@Test
@DisplayName("hasAccess should throw InvalidPermissionRequestException when client throws FeignException")
void hasAccessShouldThrowInvalidPermissionRequestExceptionWhenClientThrowsFeignException() {
FeignException feignException = new FeignException.NotFound("Not Found", createRequest(), null, null); FeignException feignException = new FeignException.NotFound("Not Found", createRequest(), null, null);
when(client.checkAccess(any(CheckAccessRequest.class))).thenThrow(feignException); when(client.checkAccess(any(CheckAccessRequest.class))).thenThrow(feignException);
assertThrows(AccessDeniedException.class, () -> assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.hasAccess(TEST_USER, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE) permissionManager.hasAccess(TEST_USER, TEST_DOMAIN, TEST_PERMISSION, TEST_SCOPE)
); );
} }
@ -254,6 +269,62 @@ class PermissionManagerTest {
)); ));
} }
@Test
@DisplayName("listPermittedScopes should return scopes from client")
void listPermittedScopesShouldReturnScopesFromClient() {
ListPermittedScopesResponse response = new ListPermittedScopesResponse();
response.setScopes(Arrays.asList("scope1", "scope2"));
when(client.listPermittedScopes(any(ListPermittedScopesRequest.class))).thenReturn(response);
List<String> scopes = permissionManager.listPermittedScopes(TEST_USER, TEST_DOMAIN, TEST_PERMISSION);
assertEquals(Arrays.asList("scope1", "scope2"), scopes);
verify(client).listPermittedScopes(argThat(request ->
TEST_USER.equals(request.getUserId()) &&
TEST_DOMAIN.equals(request.getDomain()) &&
TEST_PERMISSION.equals(request.getPermission())
));
}
@Test
@DisplayName("listPermittedScopes should throw InvalidPermissionRequestException when client throws FeignException")
void listPermittedScopesShouldThrowInvalidPermissionRequestExceptionWhenClientThrowsFeignException() {
FeignException feignException = new FeignException.InternalServerError("Internal Server Error", createRequest(), null, null);
when(client.listPermittedScopes(any(ListPermittedScopesRequest.class))).thenThrow(feignException);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(TEST_USER, TEST_DOMAIN, TEST_PERMISSION)
);
}
@Test
@DisplayName("listPermittedScopes should throw InvalidPermissionRequestException when parameters are invalid")
void listPermittedScopesShouldThrowInvalidPermissionRequestExceptionWhenParametersAreInvalid() {
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(null, TEST_DOMAIN, TEST_PERMISSION)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes("", TEST_DOMAIN, TEST_PERMISSION)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(TEST_USER, null, TEST_PERMISSION)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(TEST_USER, "", TEST_PERMISSION)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(TEST_USER, TEST_DOMAIN, null)
);
assertThrows(InvalidPermissionRequestException.class, () ->
permissionManager.listPermittedScopes(TEST_USER, TEST_DOMAIN, "")
);
}
private Request createRequest() { private Request createRequest() {
return Request.create(Request.HttpMethod.GET, "url", new HashMap<>(), null, new RequestTemplate()); return Request.create(Request.HttpMethod.GET, "url", new HashMap<>(), null, new RequestTemplate());
} }

View File

@ -1,6 +1,8 @@
package de.mummeit.pmg.service.builder; package de.mummeit.pmg.service.builder;
import de.mummeit.pmg.api.model.integration.*; import de.mummeit.pmg.api.model.integration.*;
import de.mummeit.pmg.builder.IntegrationBuilder;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import de.mummeit.pmg.api.model.integration.DomainIntegration; import de.mummeit.pmg.api.model.integration.DomainIntegration;
import de.mummeit.pmg.api.model.integration.Integration; import de.mummeit.pmg.api.model.integration.Integration;
import de.mummeit.pmg.config.AbstractPermissionManagerConfiguration;
import de.mummeit.pmg.service.PermissionManager; import de.mummeit.pmg.service.PermissionManager;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;

View File

@ -1,18 +1,19 @@
package de.mummeit.utility; package de.mummeit.utility;
import org.testcontainers.containers.GenericContainer; import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.Network; import org.testcontainers.containers.Network;
import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.GenericContainer;
public abstract class TestContainer { public abstract class TestContainer {
private static final String DB_DATABASE = "test"; private static final String DB_DATABASE = "test";
private static final String DB_USER = "postgres"; private static final String DB_USER = "postgres";
private static final String DB_PASSWORD = "super"; private static final String DB_PASSWORD = "super";
private static final String TEST_API_KEY = "test-api-key-123";
public static Network network = Network.newNetwork(); public static Network network = Network.newNetwork();
public static GenericContainer<?> postgresContainer = new PostgreSQLContainer("postgres:latest") public static GenericContainer<?> postgresContainer = new PostgreSQLContainer("postgres:latest")
.withDatabaseName(DB_DATABASE) .withDatabaseName(DB_DATABASE)
.withUsername(DB_USER) .withUsername(DB_USER)
@ -27,15 +28,21 @@ public abstract class TestContainer {
.withEnv("DB_PASSWORD", DB_PASSWORD) .withEnv("DB_PASSWORD", DB_PASSWORD)
.withEnv("DB_HOST", "testcontainer-db") .withEnv("DB_HOST", "testcontainer-db")
.withEnv("DB_PORT", "5432") .withEnv("DB_PORT", "5432")
.withEnv("AUTH_ENABLED", "true")
.withEnv("AUTH_APIKEY", TEST_API_KEY)
.withNetwork(network) .withNetwork(network)
.withNetworkAliases("permission-manager"); .withNetworkAliases("permission-manager");
static { static {
postgresContainer.start(); postgresContainer.start();
permissionManagerContainer.start(); permissionManagerContainer.start();
System.setProperty("permission-manager.url", "http://localhost:" + permissionManagerContainer.getFirstMappedPort()); }
@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
registry.add("permission-manager.url",
() -> String.format("http://localhost:%d", permissionManagerContainer.getFirstMappedPort()));
registry.add("permission-manager.auth.enabled", () -> "true");
registry.add("permission-manager.auth.api-key", () -> TEST_API_KEY);
} }
} }