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:
5
.idea/compiler.xml
generated
5
.idea/compiler.xml
generated
@ -10,4 +10,9 @@
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
<component name="JavacSettings">
|
||||
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
|
||||
<module name="permission-manager-sdk" options="-parameters" />
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic",
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
15
do
15
do
@ -2,19 +2,6 @@
|
||||
set -euo pipefail
|
||||
script_dir="$(cd "$(dirname "${0}")"; pwd -P)"
|
||||
|
||||
|
||||
app_name='permission-manager'
|
||||
|
||||
postgres_image="postgres:16-alpine"
|
||||
postgres_port=16060
|
||||
postgres_container_name="pmg-db"
|
||||
|
||||
go_junit_report_version='latest'
|
||||
gocov_version='latest'
|
||||
gocov_xml_version='latest'
|
||||
linter_version='v1.62.0'
|
||||
swag_version='v1.16.3'
|
||||
|
||||
grey='\033[0;37m'
|
||||
red='\033[1;31m'
|
||||
green='\033[1;32m'
|
||||
@ -29,7 +16,7 @@ function log {
|
||||
|
||||
## full-release : creates and publishes a new release to maven central
|
||||
function task_full_release() {
|
||||
gitea_token="$(cat ${script_dir}/.gitea-token)"
|
||||
gitea_token="$(cat "${script_dir}/.gitea-token")"
|
||||
|
||||
task_build
|
||||
mvn jreleaser:full-release -Dgitea.token="$gitea_token"
|
||||
|
106
pom.xml
106
pom.xml
@ -21,6 +21,8 @@
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<spring-boot.version>3.3.0</spring-boot.version>
|
||||
<spring-cloud.version>2023.0.2</spring-cloud.version>
|
||||
</properties>
|
||||
|
||||
<licenses>
|
||||
@ -31,32 +33,108 @@
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- Spring Boot BOM -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- Spring Cloud BOM -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Feign -->
|
||||
<!-- Spring Cloud -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>4.1.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jakarta -->
|
||||
<dependency>
|
||||
<groupId>jakarta.servlet</groupId>
|
||||
<artifactId>jakarta.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Feign -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign.form</groupId>
|
||||
<artifactId>feign-form</artifactId>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
<!-- Others -->
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.32</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>1.20.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<version>1.20.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<developers>
|
||||
@ -68,13 +146,11 @@
|
||||
|
||||
<scm>
|
||||
<connection>scm:git:https://git.mumme-it.de/Mumme-IT/permission-manager-sdk-java.git</connection>
|
||||
<developerConnection>scm:git:https://git.mumme-it.de/Mumme-IT/permission-manager-sdk-java.git
|
||||
</developerConnection>
|
||||
<developerConnection>scm:git:https://git.mumme-it.de/Mumme-IT/permission-manager-sdk-java.git</developerConnection>
|
||||
<url>https://git.mumme-it.de/Mumme-IT/permission-manager-sdk-java.git</url>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
@ -87,6 +163,11 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -109,13 +190,12 @@
|
||||
<plugin>
|
||||
<groupId>org.jreleaser</groupId>
|
||||
<artifactId>jreleaser-maven-plugin</artifactId>
|
||||
<version>1.15.0</version>
|
||||
<configuration>
|
||||
<jreleaser>
|
||||
<signing>
|
||||
<active>ALWAYS</active>
|
||||
<mode>COMMAND</mode>
|
||||
<armored>true</armored>#
|
||||
<armored>true</armored>
|
||||
<command>
|
||||
</command>
|
||||
</signing>
|
||||
|
@ -1,13 +1,22 @@
|
||||
package de.mummeit.common.config;
|
||||
|
||||
import feign.Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
@EnableFeignClients
|
||||
@ComponentScan(basePackages = "de.mummeit")
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = "de.mummeit")
|
||||
@EnableFeignClients(basePackages = "de.mummeit")
|
||||
@PropertySource(value = "classpath:permission-manager-sdk-application.yaml")
|
||||
public class PermissionManagerSdkConfiguration {
|
||||
|
||||
@Bean
|
||||
public Client feignClient() {
|
||||
return new ApacheHttpClient(HttpClients.createDefault());
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package de.mummeit.pmg.api;
|
||||
|
||||
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
|
||||
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.Integration;
|
||||
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 org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(
|
||||
name = "permission-manager",
|
||||
url = "${permission-manager.url}"
|
||||
)
|
||||
public interface PermissionManagerClient {
|
||||
|
||||
@GetMapping("/health")
|
||||
String getHealthStatus();
|
||||
|
||||
// Access Management
|
||||
@PostMapping("/api/v1/access/check")
|
||||
PermittedResponse checkAccess(@RequestBody CheckAccessRequest request);
|
||||
|
||||
@PostMapping("/api/v1/access/permit")
|
||||
void permitAccess(@RequestBody PermitRequest request);
|
||||
|
||||
@PostMapping("/api/v1/access/permits/search")
|
||||
List<Permission> searchPermits(@RequestBody SearchPermitRequest request);
|
||||
|
||||
@PatchMapping("/api/v1/access/revoke")
|
||||
void revokeAccess(@RequestBody PermitRequest request);
|
||||
|
||||
@PatchMapping("/api/v1/access/revoke/scope")
|
||||
void revokeScopeAccess(@RequestBody RevokeScopeAccessRequest request);
|
||||
|
||||
@PatchMapping("/api/v1/access/revoke/user")
|
||||
void revokeUserAccess(@RequestBody RevokeUserAccessRequest request);
|
||||
|
||||
// Domain Management
|
||||
@PostMapping("/api/v1/domains")
|
||||
Domain createDomain(@RequestBody Domain domain);
|
||||
|
||||
@GetMapping("/api/v1/domains/{domain}")
|
||||
Domain getDomain(@PathVariable("domain") String domain);
|
||||
|
||||
@PutMapping("/api/v1/domains/{domain}")
|
||||
Domain updateDomain(@PathVariable("domain") String domain, @RequestBody Domain domainRequest);
|
||||
|
||||
@DeleteMapping("/api/v1/domains/{domain}")
|
||||
void deleteDomain(@PathVariable("domain") String domain);
|
||||
|
||||
// Permission Management
|
||||
@PostMapping("/api/v1/domains/{domain}/permissions")
|
||||
Permission createPermission(@PathVariable("domain") String domain, @RequestBody Permission permission);
|
||||
|
||||
@GetMapping("/api/v1/domains/{domain}/permissions/{permission}")
|
||||
Permission getPermission(@PathVariable("domain") String domain, @PathVariable("permission") String permission);
|
||||
|
||||
@PutMapping("/api/v1/domains/{domain}/permissions/{permission}")
|
||||
Permission updatePermission(
|
||||
@PathVariable("domain") String domain,
|
||||
@PathVariable("permission") String permission,
|
||||
@RequestBody Permission permissionRequest
|
||||
);
|
||||
|
||||
@DeleteMapping("/api/v1/domains/{domain}/permissions/{permission}")
|
||||
void deletePermission(@PathVariable("domain") String domain, @PathVariable("permission") String permission);
|
||||
|
||||
// Role Management
|
||||
@PostMapping("/api/v1/domains/{domain}/roles")
|
||||
Role createRole(@PathVariable("domain") String domain, @RequestBody Role role);
|
||||
|
||||
@GetMapping("/api/v1/domains/{domain}/roles/{role}")
|
||||
Role getRole(@PathVariable("domain") String domain, @PathVariable("role") String role);
|
||||
|
||||
@PutMapping("/api/v1/domains/{domain}/roles/{role}")
|
||||
Role updateRole(
|
||||
@PathVariable("domain") String domain,
|
||||
@PathVariable("role") String role,
|
||||
@RequestBody Role roleRequest
|
||||
);
|
||||
|
||||
@DeleteMapping("/api/v1/domains/{domain}/roles/{role}")
|
||||
void deleteRole(@PathVariable("domain") String domain, @PathVariable("role") String role);
|
||||
|
||||
// Integration
|
||||
@PostMapping("/api/v1/integration/perform")
|
||||
void performIntegration(List<Integration<?>> integrations);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package de.mummeit.pmg.api;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@FeignClient(
|
||||
name = "pmg",
|
||||
url = "${pmg.url}"
|
||||
)
|
||||
public interface PmgClient {
|
||||
|
||||
@GetMapping("/health")
|
||||
public String getHealthStatus();
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package de.mummeit.pmg.api.annotation;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Annotation to enforce permission checks on methods.
|
||||
* The permission check will be performed before the method is executed.
|
||||
* If the permission check fails, an AccessDeniedException will be thrown.
|
||||
*/
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Repeatable(RequiresPermissions.class)
|
||||
public @interface RequiresPermission {
|
||||
|
||||
/**
|
||||
* The domain to check permissions for.
|
||||
*/
|
||||
String domain();
|
||||
|
||||
/**
|
||||
* The permission to check.
|
||||
*/
|
||||
String permission();
|
||||
|
||||
/**
|
||||
* SpEL expression to determine the scope of the permission check.
|
||||
* If empty, no scope will be used.
|
||||
*/
|
||||
String scope() default "";
|
||||
|
||||
/**
|
||||
* SpEL expression to determine the user ID for the permission check.
|
||||
* By default, uses the security service to get the current user ID.
|
||||
* The expression can reference:
|
||||
* - Method parameters by name
|
||||
* - Spring beans with the '@' prefix (e.g., '@securityService.getCurrentUserId()')
|
||||
* - The HTTP request parameters with '#request.getParameter("paramName")'
|
||||
*/
|
||||
String userIdExpression() default "@securityService.getCurrentUserId()";
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package de.mummeit.pmg.api.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Container annotation for multiple {@link RequiresPermission} annotations.
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RequiresPermissions {
|
||||
RequiresPermission[] value();
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package de.mummeit.pmg.api.aspect;
|
||||
|
||||
import de.mummeit.pmg.api.PermissionManagerClient;
|
||||
import de.mummeit.pmg.api.annotation.RequiresPermission;
|
||||
import de.mummeit.pmg.api.model.access.request.CheckAccessRequest;
|
||||
import de.mummeit.pmg.service.exception.AccessDeniedException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class PermissionCheckAspect {
|
||||
|
||||
private final PermissionManagerClient permissionManagerClient;
|
||||
private final ApplicationContext applicationContext;
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@Around("@annotation(de.mummeit.pmg.api.annotation.RequiresPermission)")
|
||||
public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
|
||||
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
|
||||
|
||||
// Add method parameters to the context
|
||||
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
|
||||
if (parameterNames != null) {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
for (int i = 0; i < parameterNames.length; i++) {
|
||||
context.setVariable(parameterNames[i], args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
String userId = resolveUserId(context, annotation);
|
||||
String scope = annotation.scope().isEmpty() ? "" : parser.parseExpression(annotation.scope()).getValue(context, String.class);
|
||||
|
||||
CheckAccessRequest request = new CheckAccessRequest();
|
||||
request.setDomain(annotation.domain());
|
||||
request.setPermission(annotation.permission());
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
|
||||
if (!permissionManagerClient.checkAccess(request).isPermitted()) {
|
||||
throw new AccessDeniedException(
|
||||
"Access denied",
|
||||
userId,
|
||||
annotation.domain(),
|
||||
annotation.permission(),
|
||||
scope
|
||||
);
|
||||
}
|
||||
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
private String resolveUserId(EvaluationContext context, RequiresPermission annotation) {
|
||||
// First try to evaluate the expression
|
||||
try {
|
||||
String userId = parser.parseExpression(annotation.userIdExpression()).getValue(context, String.class);
|
||||
if (userId != null) {
|
||||
return userId;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Fall through to request parameter check
|
||||
}
|
||||
|
||||
// Fallback to request parameter
|
||||
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
|
||||
.map(attributes -> ((ServletRequestAttributes) attributes).getRequest())
|
||||
.map(request -> request.getParameter("userId"))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Could not resolve userId"));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CheckAccessRequest {
|
||||
private String domain;
|
||||
private String userId;
|
||||
private String scope;
|
||||
private String permission;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Permit {
|
||||
private String domain;
|
||||
private List<String> roles;
|
||||
private List<String> permissions;
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class PermitRequest {
|
||||
private List<Permit> permits;
|
||||
private String scope;
|
||||
private String userId;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RevokeScopeAccessRequest {
|
||||
private String scope;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class RevokeUserAccessRequest {
|
||||
private String userId;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package de.mummeit.pmg.api.model.access.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SearchPermitRequest {
|
||||
private String scope;
|
||||
private String userId;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.mummeit.pmg.api.model.access.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class PermittedResponse {
|
||||
private boolean permitted;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package de.mummeit.pmg.api.model.integration;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
public class DomainIntegration extends Integration<DomainIntegration.Data> {
|
||||
|
||||
@Builder
|
||||
public DomainIntegration(String id, Action action, Data data) {
|
||||
super(id, Entity.domain, action, data);
|
||||
}
|
||||
|
||||
@lombok.Data
|
||||
@Builder
|
||||
public static class Data {
|
||||
private String name, oldName, description;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package de.mummeit.pmg.api.model.integration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entity")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public abstract class Integration<T> {
|
||||
public enum Action {
|
||||
create,
|
||||
update,
|
||||
delete
|
||||
}
|
||||
|
||||
public enum Entity {
|
||||
domain,
|
||||
role,
|
||||
permission,
|
||||
permission_role_relation,
|
||||
}
|
||||
|
||||
private String id;
|
||||
private Entity entity;
|
||||
private Action action;
|
||||
|
||||
protected T data;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package de.mummeit.pmg.api.model.integration;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
public class PermissionIntegration extends Integration<PermissionIntegration.Data> {
|
||||
|
||||
@Builder
|
||||
public PermissionIntegration(String id, Action action, Data data) {
|
||||
super(id, Entity.permission, action, data);
|
||||
}
|
||||
|
||||
@Builder
|
||||
@lombok.Data
|
||||
public static class Data {
|
||||
private String domain, name, oldName, description;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package de.mummeit.pmg.api.model.integration;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
public class RoleIntegration extends Integration<RoleIntegration.Data> {
|
||||
|
||||
@Builder
|
||||
public RoleIntegration(String id, Action action, Data data) {
|
||||
super(id, Entity.role, action, data);
|
||||
}
|
||||
|
||||
@Builder
|
||||
@lombok.Data
|
||||
public static class Data {
|
||||
private String domain, name, oldName, description;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package de.mummeit.pmg.api.model.integration;
|
||||
|
||||
import lombok.Builder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class RolePermissionRelationIntegration extends Integration<RolePermissionRelationIntegration.Data> {
|
||||
|
||||
@Builder
|
||||
public RolePermissionRelationIntegration(String id, Action action, Data data) {
|
||||
super(id, Entity.permission_role_relation, action, data);
|
||||
}
|
||||
|
||||
@Builder
|
||||
@lombok.Data
|
||||
public static class Data {
|
||||
private String domain, role;
|
||||
private List<String> permissions;
|
||||
}
|
||||
}
|
12
src/main/java/de/mummeit/pmg/api/model/structure/Domain.java
Normal file
12
src/main/java/de/mummeit/pmg/api/model/structure/Domain.java
Normal file
@ -0,0 +1,12 @@
|
||||
package de.mummeit.pmg.api.model.structure;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Domain {
|
||||
private String name;
|
||||
private String description;
|
||||
private List<Permission> permissions;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package de.mummeit.pmg.api.model.structure;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Permission {
|
||||
private Domain domain;
|
||||
private String name;
|
||||
private String description;
|
||||
}
|
13
src/main/java/de/mummeit/pmg/api/model/structure/Role.java
Normal file
13
src/main/java/de/mummeit/pmg/api/model/structure/Role.java
Normal file
@ -0,0 +1,13 @@
|
||||
package de.mummeit.pmg.api.model.structure;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class Role {
|
||||
private Domain domain;
|
||||
private String name;
|
||||
private String description;
|
||||
private List<Permission> permissions;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
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 to retrieve the current user's ID from Spring Security context.
|
||||
* This is the default user ID provider for the permission manager.
|
||||
*/
|
||||
@Service
|
||||
public class SecurityService {
|
||||
|
||||
/**
|
||||
* Gets the current user's ID from the Spring Security context.
|
||||
*
|
||||
* @return the current user's ID
|
||||
* @throws IllegalStateException if no authenticated user is found
|
||||
*/
|
||||
public String getCurrentUserId() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
throw new IllegalStateException("No authenticated user found in security context");
|
||||
}
|
||||
return authentication.getName();
|
||||
}
|
||||
}
|
299
src/main/java/de/mummeit/pmg/builder/IntegrationBuilder.java
Normal file
299
src/main/java/de/mummeit/pmg/builder/IntegrationBuilder.java
Normal file
@ -0,0 +1,299 @@
|
||||
package de.mummeit.pmg.service.builder;
|
||||
|
||||
import de.mummeit.pmg.api.model.integration.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A fluent builder for creating Permission Manager integrations.
|
||||
* This builder provides a type-safe way to create various types of integrations including:
|
||||
* - Domain integrations (create, update, delete)
|
||||
* - Permission integrations (create, update, delete)
|
||||
* - Role integrations (create, update, delete)
|
||||
* - Role-Permission relation integrations
|
||||
*
|
||||
* Example usage:
|
||||
* {@code
|
||||
* List<Integration<?>> integrations = IntegrationBuilder.create()
|
||||
* .createDomain("my-domain", "My Domain")
|
||||
* .addPermission("read", "Read Permission")
|
||||
* .addRole("user", "User Role")
|
||||
* .assignPermissionsToRole("user", Arrays.asList("read"))
|
||||
* .build();
|
||||
* }
|
||||
*/
|
||||
public class IntegrationBuilder {
|
||||
private final List<Integration<?>> integrations = new ArrayList<>();
|
||||
private String currentDomain;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the IntegrationBuilder.
|
||||
*
|
||||
* @return A new IntegrationBuilder instance
|
||||
*/
|
||||
public static IntegrationBuilder create() {
|
||||
return new IntegrationBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a domain as the context for subsequent operations without creating an integration.
|
||||
* This is useful when you need to perform operations on an existing domain.
|
||||
*
|
||||
* @param name The name of the domain to select
|
||||
* @return This builder instance for method chaining
|
||||
*/
|
||||
public IntegrationBuilder selectDomain(String name) {
|
||||
this.currentDomain = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new domain integration with the specified name and description.
|
||||
* This also sets the domain context for subsequent operations.
|
||||
*
|
||||
* @param name The name of the domain
|
||||
* @param description The description of the domain
|
||||
* @return This builder instance for method chaining
|
||||
*/
|
||||
public IntegrationBuilder createDomain(String name, String description) {
|
||||
this.currentDomain = name;
|
||||
integrations.add(DomainIntegration.builder()
|
||||
.id(generateIntegrationId("domain", name, Integration.Action.create))
|
||||
.action(Integration.Action.create)
|
||||
.data(DomainIntegration.Data.builder()
|
||||
.name(name)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing domain with a new name and/or description.
|
||||
* This also sets the domain context to the new name for subsequent operations.
|
||||
*
|
||||
* @param oldName The current name of the domain to update
|
||||
* @param newName The new name for the domain
|
||||
* @param description The new description for the domain
|
||||
* @return This builder instance for method chaining
|
||||
*/
|
||||
public IntegrationBuilder updateDomain(String oldName, String newName, String description) {
|
||||
integrations.add(DomainIntegration.builder()
|
||||
.id(generateIntegrationId("domain", oldName, Integration.Action.update))
|
||||
.action(Integration.Action.update)
|
||||
.data(DomainIntegration.Data.builder()
|
||||
.name(newName)
|
||||
.oldName(oldName)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
this.currentDomain = newName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a domain.
|
||||
*
|
||||
* @param name The name of the domain to delete
|
||||
* @return This builder instance for method chaining
|
||||
*/
|
||||
public IntegrationBuilder deleteDomain(String name) {
|
||||
integrations.add(DomainIntegration.builder()
|
||||
.id(generateIntegrationId("domain", name, Integration.Action.delete))
|
||||
.action(Integration.Action.delete)
|
||||
.data(DomainIntegration.Data.builder()
|
||||
.name(name)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new permission to the current domain.
|
||||
*
|
||||
* @param name The name of the permission
|
||||
* @param description The description of the permission
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder addPermission(String name, String description) {
|
||||
validateDomain();
|
||||
integrations.add(PermissionIntegration.builder()
|
||||
.id(generateIntegrationId("permission", currentDomain + ":" + name, Integration.Action.create))
|
||||
.action(Integration.Action.create)
|
||||
.data(PermissionIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(name)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing permission in the current domain.
|
||||
*
|
||||
* @param oldName The current name of the permission to update
|
||||
* @param newName The new name for the permission
|
||||
* @param description The new description for the permission
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder updatePermission(String oldName, String newName, String description) {
|
||||
validateDomain();
|
||||
integrations.add(PermissionIntegration.builder()
|
||||
.id(generateIntegrationId("permission", currentDomain + ":" + oldName, Integration.Action.update))
|
||||
.action(Integration.Action.update)
|
||||
.data(PermissionIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(newName)
|
||||
.oldName(oldName)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a permission from the current domain.
|
||||
*
|
||||
* @param name The name of the permission to remove
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder removePermission(String name) {
|
||||
validateDomain();
|
||||
integrations.add(PermissionIntegration.builder()
|
||||
.id(generateIntegrationId("permission", currentDomain + ":" + name, Integration.Action.delete))
|
||||
.action(Integration.Action.delete)
|
||||
.data(PermissionIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(name)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new role to the current domain.
|
||||
*
|
||||
* @param name The name of the role
|
||||
* @param description The description of the role
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder addRole(String name, String description) {
|
||||
validateDomain();
|
||||
integrations.add(RoleIntegration.builder()
|
||||
.id(generateIntegrationId("role", currentDomain + ":" + name, Integration.Action.create))
|
||||
.action(Integration.Action.create)
|
||||
.data(RoleIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(name)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing role in the current domain.
|
||||
*
|
||||
* @param oldName The current name of the role to update
|
||||
* @param newName The new name for the role
|
||||
* @param description The new description for the role
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder updateRole(String oldName, String newName, String description) {
|
||||
validateDomain();
|
||||
integrations.add(RoleIntegration.builder()
|
||||
.id(generateIntegrationId("role", currentDomain + ":" + oldName, Integration.Action.update))
|
||||
.action(Integration.Action.update)
|
||||
.data(RoleIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(newName)
|
||||
.oldName(oldName)
|
||||
.description(description)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a role from the current domain.
|
||||
*
|
||||
* @param name The name of the role to remove
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder removeRole(String name) {
|
||||
validateDomain();
|
||||
integrations.add(RoleIntegration.builder()
|
||||
.id(generateIntegrationId("role", currentDomain + ":" + name, Integration.Action.delete))
|
||||
.action(Integration.Action.delete)
|
||||
.data(RoleIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.name(name)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a list of permissions to a role in the current domain.
|
||||
*
|
||||
* @param role The name of the role to assign permissions to
|
||||
* @param permissions List of permission names to assign to the role
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
public IntegrationBuilder assignPermissionsToRole(String role, List<String> permissions) {
|
||||
validateDomain();
|
||||
String permissionList = String.join(",", permissions);
|
||||
integrations.add(RolePermissionRelationIntegration.builder()
|
||||
.id(generateIntegrationId("role-permissions", currentDomain + ":" + role + ":" + permissionList, Integration.Action.create))
|
||||
.action(Integration.Action.create)
|
||||
.data(RolePermissionRelationIntegration.Data.builder()
|
||||
.domain(currentDomain)
|
||||
.role(role)
|
||||
.permissions(permissions)
|
||||
.build())
|
||||
.build());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the list of integrations created by this builder.
|
||||
*
|
||||
* @return A new list containing all the integrations created by this builder
|
||||
*/
|
||||
public List<Integration<?>> build() {
|
||||
return new ArrayList<>(integrations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a domain context has been set by a previous domain operation.
|
||||
*
|
||||
* @throws IllegalStateException if no domain context has been set
|
||||
*/
|
||||
private void validateDomain() {
|
||||
if (currentDomain == null || currentDomain.trim().isEmpty()) {
|
||||
throw new IllegalStateException("No domain context set. Create, update, or select a domain first.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a deterministic integration ID based on the integration type, name, and action.
|
||||
* This ensures that the same integration will always have the same ID across different runs.
|
||||
*
|
||||
* @param type The type of integration (domain, permission, role, etc.)
|
||||
* @param name The unique name or identifier for this integration
|
||||
* @param action The action being performed
|
||||
* @return A deterministic ID string
|
||||
*/
|
||||
private String generateIntegrationId(String type, String name, Integration.Action action) {
|
||||
return String.format("%s:%s:%s", type, name, action.name().toLowerCase());
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
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.Integration;
|
||||
import de.mummeit.pmg.service.PermissionManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract configuration class for Permission Manager integration.
|
||||
* Implementing classes can provide integrations either by overriding getIntegrations()
|
||||
* or by providing a JSON file path in getIntegrationsJsonPath().
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractPermissionManagerConfiguration {
|
||||
|
||||
private final PermissionManager permissionManager;
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* Override this method to provide integrations programmatically.
|
||||
* By default, returns null, which means no integrations will be performed unless
|
||||
* getIntegrationsJsonPath() returns a valid path.
|
||||
*
|
||||
* @return List of integrations to perform, or null if no integrations should be performed
|
||||
*/
|
||||
protected List<Integration<?>> getIntegrations() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to provide the path to a JSON file containing integrations.
|
||||
* The JSON file should contain an array of Integration objects.
|
||||
* By default, returns null, which means no JSON file will be loaded.
|
||||
*
|
||||
* @return Path to the JSON file, or null if no file should be loaded
|
||||
*/
|
||||
protected String getIntegrationsJsonPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads integrations from a JSON file.
|
||||
*
|
||||
* @param path Path to the JSON file
|
||||
* @return List of integrations
|
||||
* @throws IOException if the file cannot be read or parsed
|
||||
*/
|
||||
protected List<Integration<?>> loadIntegrationsFromJson(String path) throws IOException {
|
||||
Resource resource = resourceLoader.getResource(path);
|
||||
try (InputStream inputStream = resource.getInputStream()) {
|
||||
return objectMapper.readValue(inputStream, new TypeReference<List<Integration<?>>>() {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener that performs integrations when the application is ready.
|
||||
* This method will first try to get integrations from getIntegrations(),
|
||||
* and if that returns null, it will try to load integrations from the JSON file
|
||||
* specified by getIntegrationsJsonPath().
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void performIntegrationsOnStartup() {
|
||||
try {
|
||||
List<Integration<?>> integrations = getIntegrations();
|
||||
if (integrations == null) {
|
||||
String jsonPath = getIntegrationsJsonPath();
|
||||
if (jsonPath != null) {
|
||||
integrations = loadIntegrationsFromJson(jsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (integrations != null && !integrations.isEmpty()) {
|
||||
log.info("Performing {} integrations on startup", integrations.size());
|
||||
permissionManager.performIntegration(integrations);
|
||||
log.info("Successfully performed integrations");
|
||||
} else {
|
||||
log.info("No integrations to perform");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to perform integrations on startup", e);
|
||||
throw new RuntimeException("Failed to perform integrations on startup", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package de.mummeit.pmg.service.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when access is denied for a user.
|
||||
*/
|
||||
public class AccessDeniedException extends PermissionManagerException {
|
||||
private final String userId;
|
||||
private final String domain;
|
||||
private final String permission;
|
||||
private final String scope;
|
||||
|
||||
public AccessDeniedException(String message, String userId, String domain, String permission, String scope) {
|
||||
super(message);
|
||||
this.userId = userId;
|
||||
this.domain = domain;
|
||||
this.permission = permission;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public AccessDeniedException(String message, String userId, String domain, String permission, String scope, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.userId = userId;
|
||||
this.domain = domain;
|
||||
this.permission = permission;
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public String getPermission() {
|
||||
return permission;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package de.mummeit.pmg.service.exception;
|
||||
|
||||
import de.mummeit.pmg.api.model.integration.Integration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Exception thrown when an integration operation fails.
|
||||
*/
|
||||
public class IntegrationFailedException extends PermissionManagerException {
|
||||
private final List<Integration<?>> failedIntegrations;
|
||||
|
||||
public IntegrationFailedException(String message, List<Integration<?>> failedIntegrations) {
|
||||
super(message);
|
||||
this.failedIntegrations = failedIntegrations;
|
||||
}
|
||||
|
||||
public IntegrationFailedException(String message, List<Integration<?>> failedIntegrations, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.failedIntegrations = failedIntegrations;
|
||||
}
|
||||
|
||||
public List<Integration<?>> getFailedIntegrations() {
|
||||
return failedIntegrations;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package de.mummeit.pmg.service.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when a permission request is invalid.
|
||||
*/
|
||||
public class InvalidPermissionRequestException extends PermissionManagerException {
|
||||
private final String reason;
|
||||
|
||||
public InvalidPermissionRequestException(String message, String reason) {
|
||||
super(message);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public InvalidPermissionRequestException(String message, String reason, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package de.mummeit.pmg.service.exception;
|
||||
|
||||
/**
|
||||
* Base exception class for all Permission Manager related exceptions.
|
||||
*/
|
||||
public class PermissionManagerException extends RuntimeException {
|
||||
public PermissionManagerException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PermissionManagerException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
376
src/main/java/de/mummeit/pmg/service/PermissionManager.java
Normal file
376
src/main/java/de/mummeit/pmg/service/PermissionManager.java
Normal file
@ -0,0 +1,376 @@
|
||||
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 lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PermissionManager {
|
||||
|
||||
private final PermissionManagerClient client;
|
||||
|
||||
/**
|
||||
* Checks if a user has access to a specific permission in a domain and scope.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param domain The domain name
|
||||
* @param permission The permission name
|
||||
* @param scope The scope
|
||||
* @return true if the user has access, false otherwise
|
||||
* @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) {
|
||||
validateParameters(userId, domain, permission, scope);
|
||||
|
||||
try {
|
||||
CheckAccessRequest request = new CheckAccessRequest();
|
||||
request.setUserId(userId);
|
||||
request.setDomain(domain);
|
||||
request.setPermission(permission);
|
||||
request.setScope(scope);
|
||||
|
||||
PermittedResponse response = client.checkAccess(request);
|
||||
return response.isPermitted();
|
||||
} catch (FeignException e) {
|
||||
throw new AccessDeniedException(
|
||||
"Failed to check access",
|
||||
userId,
|
||||
domain,
|
||||
permission,
|
||||
scope,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants a user access to specific permissions and roles in a domain and scope.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param domain The domain name
|
||||
* @param permissions List of permission names to grant
|
||||
* @param roles List of role names to grant
|
||||
* @param scope The scope
|
||||
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
|
||||
*/
|
||||
public void grantAccess(String userId, String domain, List<String> permissions, List<String> roles, String scope) {
|
||||
validateParameters(userId, domain, scope);
|
||||
validatePermissionsAndRoles(permissions, roles);
|
||||
|
||||
try {
|
||||
PermitRequest request = new PermitRequest();
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
|
||||
Permit permit = new Permit();
|
||||
permit.setDomain(domain);
|
||||
permit.setPermissions(permissions);
|
||||
permit.setRoles(roles);
|
||||
|
||||
request.setPermits(Collections.singletonList(permit));
|
||||
client.permitAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to grant access",
|
||||
"Error occurred while granting permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes a user's access to specific permissions and roles in a domain and scope.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param domain The domain name
|
||||
* @param permissions List of permission names to revoke
|
||||
* @param roles List of role names to revoke
|
||||
* @param scope The scope
|
||||
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
|
||||
*/
|
||||
public void revokeAccess(String userId, String domain, List<String> permissions, List<String> roles, String scope) {
|
||||
validateParameters(userId, domain, scope);
|
||||
validatePermissionsAndRoles(permissions, roles);
|
||||
|
||||
try {
|
||||
PermitRequest request = new PermitRequest();
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
|
||||
Permit permit = new Permit();
|
||||
permit.setDomain(domain);
|
||||
permit.setPermissions(permissions);
|
||||
permit.setRoles(roles);
|
||||
|
||||
request.setPermits(Collections.singletonList(permit));
|
||||
client.revokeAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to revoke access",
|
||||
"Error occurred while revoking permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes all access for a specific user.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @throws InvalidPermissionRequestException if the userId is null or empty
|
||||
*/
|
||||
public void revokeAllUserAccess(String userId) {
|
||||
validateUserId(userId);
|
||||
|
||||
try {
|
||||
RevokeUserAccessRequest request = new RevokeUserAccessRequest();
|
||||
request.setUserId(userId);
|
||||
client.revokeUserAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to revoke all user access",
|
||||
"Error occurred while revoking all user permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes all access for a specific scope.
|
||||
*
|
||||
* @param scope The scope to revoke all access from
|
||||
* @throws InvalidPermissionRequestException if the scope is null or empty
|
||||
*/
|
||||
public void revokeAllScopeAccess(String scope) {
|
||||
validateScope(scope);
|
||||
|
||||
try {
|
||||
RevokeScopeAccessRequest request = new RevokeScopeAccessRequest();
|
||||
request.setScope(scope);
|
||||
client.revokeScopeAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to revoke all scope access",
|
||||
"Error occurred while revoking all scope permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for all permissions granted to a user in a specific scope.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param scope The scope to search in
|
||||
* @return List of permissions granted to the user
|
||||
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
|
||||
*/
|
||||
public List<Permission> findUserPermissions(String userId, String scope) {
|
||||
validateParameters(userId, scope);
|
||||
|
||||
try {
|
||||
SearchPermitRequest request = new SearchPermitRequest();
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
return client.searchPermits(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to find user permissions",
|
||||
"Error occurred while searching for user permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs integration operations.
|
||||
*
|
||||
* @param integrations List of integrations to perform
|
||||
* @throws IntegrationFailedException if the integration operations fail
|
||||
* @throws InvalidPermissionRequestException if the integrations list is null or empty
|
||||
*/
|
||||
public void performIntegration(List<Integration<?>> integrations) {
|
||||
if (integrations == null || integrations.isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid integration request",
|
||||
"Integrations list cannot be null or empty"
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
client.performIntegration(integrations);
|
||||
} catch (FeignException e) {
|
||||
throw new IntegrationFailedException(
|
||||
"Failed to perform integrations",
|
||||
integrations,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grants multiple permissions and roles across different domains to a user.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param permits List of permits containing domain-specific permissions and roles
|
||||
* @param scope The scope
|
||||
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
|
||||
*/
|
||||
public void grantMultiDomainAccess(String userId, List<Permit> permits, String scope) {
|
||||
validateParameters(userId, scope);
|
||||
validatePermits(permits);
|
||||
|
||||
try {
|
||||
PermitRequest request = new PermitRequest();
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
request.setPermits(permits);
|
||||
client.permitAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to grant multi-domain access",
|
||||
"Error occurred while granting multi-domain permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes multiple permissions and roles across different domains from a user.
|
||||
*
|
||||
* @param userId The ID of the user
|
||||
* @param permits List of permits containing domain-specific permissions and roles
|
||||
* @param scope The scope
|
||||
* @throws InvalidPermissionRequestException if any of the required parameters are invalid
|
||||
*/
|
||||
public void revokeMultiDomainAccess(String userId, List<Permit> permits, String scope) {
|
||||
validateParameters(userId, scope);
|
||||
validatePermits(permits);
|
||||
|
||||
try {
|
||||
PermitRequest request = new PermitRequest();
|
||||
request.setUserId(userId);
|
||||
request.setScope(scope);
|
||||
request.setPermits(permits);
|
||||
client.revokeAccess(request);
|
||||
} catch (FeignException e) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Failed to revoke multi-domain access",
|
||||
"Error occurred while revoking multi-domain permissions",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateParameters(String userId, String domain, String permission, String scope) {
|
||||
validateUserId(userId);
|
||||
validateDomain(domain);
|
||||
validatePermission(permission);
|
||||
validateScope(scope);
|
||||
}
|
||||
|
||||
private void validateParameters(String userId, String domain, String scope) {
|
||||
validateUserId(userId);
|
||||
validateDomain(domain);
|
||||
validateScope(scope);
|
||||
}
|
||||
|
||||
private void validateParameters(String userId, String scope) {
|
||||
validateUserId(userId);
|
||||
validateScope(scope);
|
||||
}
|
||||
|
||||
private void validateUserId(String userId) {
|
||||
if (userId == null || userId.trim().isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid user ID",
|
||||
"User ID cannot be null or empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDomain(String domain) {
|
||||
if (domain == null || domain.trim().isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid domain",
|
||||
"Domain cannot be null or empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePermission(String permission) {
|
||||
if (permission == null || permission.trim().isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid permission",
|
||||
"Permission cannot be null or empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateScope(String scope) {
|
||||
if (scope == null || scope.trim().isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid scope",
|
||||
"Scope cannot be null or empty"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePermissionsAndRoles(List<String> permissions, List<String> roles) {
|
||||
if ((permissions == null || permissions.isEmpty()) && (roles == null || roles.isEmpty())) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid permissions and roles",
|
||||
"At least one permission or role must be specified"
|
||||
);
|
||||
}
|
||||
|
||||
if (permissions != null && permissions.stream().anyMatch(p -> p == null || p.trim().isEmpty())) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid permissions",
|
||||
"Permissions cannot contain null or empty values"
|
||||
);
|
||||
}
|
||||
|
||||
if (roles != null && roles.stream().anyMatch(r -> r == null || r.trim().isEmpty())) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid roles",
|
||||
"Roles cannot contain null or empty values"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePermits(List<Permit> permits) {
|
||||
if (permits == null || permits.isEmpty()) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid permits",
|
||||
"Permits list cannot be null or empty"
|
||||
);
|
||||
}
|
||||
|
||||
if (permits.stream().anyMatch(Objects::isNull)) {
|
||||
throw new InvalidPermissionRequestException(
|
||||
"Invalid permits",
|
||||
"Permits list cannot contain null values"
|
||||
);
|
||||
}
|
||||
|
||||
permits.forEach(permit -> {
|
||||
validateDomain(permit.getDomain());
|
||||
validatePermissionsAndRoles(permit.getPermissions(), permit.getRoles());
|
||||
});
|
||||
}
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
spring:
|
||||
application:
|
||||
name: permisssion-manager-sdk
|
||||
feign:
|
||||
client:
|
||||
config:
|
||||
randomjokeapi:
|
||||
connect-timeout: 1000
|
||||
read-timeout: 2000
|
||||
|
||||
randomjokeapi:
|
||||
url: https://official-joke-api.appspot.com/random_joke
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
cloud:
|
||||
openfeign:
|
||||
client:
|
||||
config:
|
||||
permission-manager:
|
||||
connect-timeout: 1000
|
||||
read-timeout: 2000
|
||||
permission-manager:
|
||||
url: http://localhost:6060
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
19
src/test/java/de/mummeit/pmg/api/controller/TestConfig.java
Normal file
19
src/test/java/de/mummeit/pmg/api/controller/TestConfig.java
Normal 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 {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
260
src/test/java/de/mummeit/pmg/service/PermissionManagerTest.java
Normal file
260
src/test/java/de/mummeit/pmg/service/PermissionManagerTest.java
Normal 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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package de.mummeit.springboot;
|
||||
|
||||
import de.mummeit.utility.BaseIntegrationTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SpringBootContextTest extends BaseIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void contextLoads() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.mummeit.utility;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest(classes = SpringBootContextSpinner.class)
|
||||
public class BaseIntegrationTest extends TestContainer {
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package de.mummeit.utility;
|
||||
|
||||
import de.mummeit.common.annotations.EnablePermissionManager;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnablePermissionManager
|
||||
public class SpringBootContextSpinner {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootContextSpinner.class, args);
|
||||
}
|
||||
}
|
41
src/test/java/de/mummeit/utility/TestContainer.java
Normal file
41
src/test/java/de/mummeit/utility/TestContainer.java
Normal file
@ -0,0 +1,41 @@
|
||||
package de.mummeit.utility;
|
||||
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.containers.Network;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
|
||||
public abstract class TestContainer {
|
||||
private static final String DB_DATABASE = "test";
|
||||
private static final String DB_USER = "postgres";
|
||||
private static final String DB_PASSWORD = "super";
|
||||
|
||||
public static Network network = Network.newNetwork();
|
||||
|
||||
|
||||
public static GenericContainer<?> postgresContainer = new PostgreSQLContainer("postgres:latest")
|
||||
.withDatabaseName(DB_DATABASE)
|
||||
.withUsername(DB_USER)
|
||||
.withPassword(DB_PASSWORD)
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("testcontainer-db");
|
||||
|
||||
public static GenericContainer<?> permissionManagerContainer = new GenericContainer("mummeit/permission-manager:latest")
|
||||
.withExposedPorts(6060)
|
||||
.withEnv("DB_DATABASE", DB_DATABASE)
|
||||
.withEnv("DB_USER", DB_USER)
|
||||
.withEnv("DB_PASSWORD", DB_PASSWORD)
|
||||
.withEnv("DB_HOST", "testcontainer-db")
|
||||
.withEnv("DB_PORT", "5432")
|
||||
.withNetwork(network)
|
||||
.withNetworkAliases("permission-manager");
|
||||
|
||||
|
||||
static {
|
||||
|
||||
postgresContainer.start();
|
||||
|
||||
permissionManagerContainer.start();
|
||||
System.setProperty("permission-manager.url", "http://localhost:" + permissionManagerContainer.getFirstMappedPort());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user