# Permission Manager SDK for Java A Spring Boot SDK for integrating permission management into your Java applications. This library provides a robust and flexible way to manage user permissions across different domains and scopes. ## Features - Annotation-based permission checks - Programmatic permission management - Integration with Spring Security - Support for domain-based permissions - Scope-based access control - Multi-domain permission management - Flexible user ID resolution - API key authentication support ## Requirements - Java 21 or higher - Spring Boot 3.3.0 or higher - Spring Cloud 2023.0.2 or higher ## Installation Add the following dependency to your `pom.xml`: ```xml de.mumme-it permission-manager-sdk 0.1.1 ``` ## Configuration ### 1. Enable the SDK Add the `@EnablePermissionManager` annotation to your Spring Boot application class: ```java import de.mummeit.common.annotations.EnablePermissionManager; @SpringBootApplication @EnablePermissionManager public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` ### 2. Configuration Properties Add the following properties to your Spring Boot application properties file (`application.yml` or `application.properties`): ```yaml permission-manager: url: http://your-permission-manager-url # Required: URL of your Permission Manager instance auth: enabled: false # Optional: Enable/disable API key authentication (defaults to false) api-key: your-api-key # Required when auth.enabled is true security: enabled: true # Optional: Enable/disable security checks (defaults to true) ``` These properties can be configured in any valid Spring Boot configuration source (application.yml, application.properties, environment variables, etc.) following Spring Boot's standard property resolution order. ### 3. API Key Authentication The SDK supports API key authentication for requests to the Permission Manager service. When enabled, the SDK will automatically add an `x-api-key` header to all requests. To enable API key authentication: 1. Set `permission-manager.auth.enabled` to `true` 2. Provide your API key in `permission-manager.auth.api-key` Example configuration: ```yaml permission-manager: auth: enabled: true api-key: your-secret-api-key-here ``` Note: When authentication is enabled, the API key is required. The SDK will throw an `IllegalStateException` if authentication is enabled but no API key is provided. ### 4. Integration Configuration The SDK provides a flexible way to configure and perform integrations on application startup through the `AbstractPermissionManagerConfiguration` class. You can use this to automatically set up your permission structure (domains, permissions, roles, and their relationships) when your application starts. There are two ways to provide integrations: #### Option 1: JSON Configuration Create a JSON file containing your integration configuration: ```json [ { "id": "domain-1", "entity": "domain", "action": "create", "data": { "name": "users", "description": "User management domain" } }, { "id": "permission-1", "entity": "permission", "action": "create", "data": { "domain": "users", "name": "read", "description": "Permission to read user data" } }, { "id": "role-1", "entity": "role", "action": "create", "data": { "domain": "users", "name": "user_viewer", "description": "Role for viewing user data" } }, { "id": "role-permission-1", "entity": "role_permission_relation", "action": "create", "data": { "domain": "users", "role": "user_viewer", "permissions": ["read"] } } ] ``` Then create a configuration class that extends `AbstractPermissionManagerConfiguration`: ```java import de.mummeit.pmg.config.AbstractPermissionManagerConfiguration; import org.springframework.context.annotation.Configuration; @Configuration public class PermissionManagerIntegrationConfig extends AbstractPermissionManagerConfiguration { @Override protected String getIntegrationsJsonPath() { return "classpath:permission-integrations.json"; } } ``` #### Option 2: Programmatic Configuration Alternatively, you can build your integrations programmatically using the fluent `IntegrationBuilder`: ```java import de.mummeit.pmg.builder.IntegrationBuilder; import de.mummeit.pmg.config.AbstractPermissionManagerConfiguration; import org.springframework.context.annotation.Configuration; @Configuration public class PermissionManagerIntegrationConfig extends AbstractPermissionManagerConfiguration { @Override protected List> getIntegrations() { return IntegrationBuilder.create() // Create a domain and set it as the current context .createDomain("users", "User management domain") // Add permissions to the current domain .addPermission("read", "Permission to read user data") .addPermission("write", "Permission to write user data") // Add a role to the current domain .addRole("user_viewer", "Role for viewing user data") // Assign permissions to the role .assignPermissionsToRole("user_viewer", List.of("read")) // Create another domain with its permissions and roles .createDomain("orders", "Order management domain") .addPermission("view", "Permission to view orders") .addPermission("create", "Permission to create orders") .addRole("order_manager", "Role for managing orders") .assignPermissionsToRole("order_manager", List.of("view", "create")) .build(); } } ``` The `IntegrationBuilder` provides a fluent API for creating integrations with features like: - Domain context management (automatically tracks the current domain) - Method chaining for building complex permission structures - Type-safe operations for all integration types - Automatic generation of integration IDs Available builder methods: - Domain operations: - `createDomain(name, description)` - `updateDomain(oldName, newName, description)` - `deleteDomain(name)` - `selectDomain(name)` - Switch context without creating a domain - Permission operations: - `addPermission(name, description)` - `updatePermission(oldName, newName, description)` - `removePermission(name)` - Role operations: - `addRole(name, description)` - `updateRole(oldName, newName, description)` - `removeRole(name)` - Role-Permission relations: - `assignPermissionsToRole(role, permissions)` The builder ensures that operations are performed in the correct context by requiring a domain to be selected or created before performing domain-specific operations. The integrations will be performed automatically when your application starts up. You can implement either `getIntegrations()` or `getIntegrationsJsonPath()` - if both are implemented, `getIntegrations()` takes precedence. Available integration types: - `DomainIntegration`: Create/update domains - `PermissionIntegration`: Create/update permissions within domains - `RoleIntegration`: Create/update roles within domains - `RolePermissionRelationIntegration`: Manage relationships between roles and permissions Each integration requires: - `id`: A unique identifier for the integration - `action`: The operation to perform (`create`, `update`, or `delete`) - `data`: The entity-specific data for the integration ## Usage ### 1. Annotation-Based Permission Checks Use the `@RequiresPermission` annotation to protect your methods: ```java import de.mummeit.pmg.api.annotation.RequiresPermission; @RestController @RequestMapping("/api") public class YourController { @RequiresPermission(domain = "users", permission = "read", scope = "*") @GetMapping("/users") public List getUsers() { // This method will only execute if the user has the "read" permission in the "users" domain // The "*" scope means this permission applies to all scopes return userService.findAll(); } @RequiresPermission( domain = "orders", permission = "update", scope = "region-#orderId", // Example of prefix-based scope userIdExpression = "#request.getHeader('X-User-Id')" ) @PutMapping("/orders/{orderId}") public Order updateOrder(@PathVariable String orderId, @RequestBody Order order) { // This method checks permissions with a specific scope and custom user ID resolution return orderService.update(orderId, order); } @RequiresPermission( domain = "reports", permission = "view", scope = "region-*" // Example of wildcard scope matching all regions ) @GetMapping("/reports") public List getReports() { // This method allows access to users with permission for any region return reportService.findAll(); } } ``` ### 2. Multiple Permission Requirements You can require multiple permissions using repeated annotations: ```java @RequiresPermission(domain = "users", permission = "read") @RequiresPermission(domain = "orders", permission = "write") public void methodRequiringMultiplePermissions() { // This method requires both permissions } ``` ### 3. Programmatic Permission Management Use the `PermissionManager` class to manage permissions programmatically: ```java @Service @RequiredArgsConstructor public class YourService { private final PermissionManager permissionManager; public void grantUserAccess(String userId) { // Grant access to all user-related operations permissionManager.grantAccess( userId, "users", List.of("read", "write"), List.of("user_role"), "*" // Wildcard scope - applies to all scopes ); // Grant access to specific region permissionManager.grantAccess( userId, "reports", List.of("view"), List.of("reporter"), "region-europe" // Specific region scope ); // Grant access to all regions permissionManager.grantAccess( userId, "reports", List.of("view"), List.of("global_reporter"), "region-*" // Wildcard scope - applies to all regions ); } public boolean checkAccess(String userId) { // Check access for all scopes boolean hasGlobalAccess = permissionManager.hasAccess( userId, "users", "read", "*" ); // Check access for specific region boolean hasRegionAccess = permissionManager.hasAccess( userId, "reports", "view", "region-europe" ); return hasGlobalAccess && hasRegionAccess; } public void revokeAccess(String userId) { permissionManager.revokeAccess( userId, "users", List.of("read", "write"), List.of("user_role"), "global" ); } } ``` ### 4. Multi-Domain Permission Management ```java List permits = List.of( new Permit("users", List.of("read"), List.of("user_role")), new Permit("orders", List.of("write"), List.of("order_manager")) ); permissionManager.grantMultiDomainAccess(userId, permits, "global"); ``` ### 5. User Permission Queries ```java List userPermissions = permissionManager.findUserPermissions(userId, "global"); ``` ## Exception Handling The SDK throws the following exceptions: - `AccessDeniedException`: When a permission check fails - `InvalidPermissionRequestException`: When invalid parameters are provided - `IntegrationFailedException`: When integration operations fail Example exception handler: ```java @ControllerAdvice public class PermissionExceptionHandler { @ExceptionHandler(AccessDeniedException.class) public ResponseEntity handleAccessDenied(AccessDeniedException e) { return ResponseEntity.status(HttpStatus.FORBIDDEN) .body("Access denied: " + e.getMessage()); } } ``` ## Best Practices 1. **Scope Usage**: - Use scopes to implement fine-grained access control at the resource level - Leverage wildcards (`*`) for global access - Use prefix-based wildcards (e.g., `region-*`) for category-wide access - Be consistent with scope naming conventions (e.g., `region-europe`, `region-asia`) 2. **User ID Resolution**: Customize user ID resolution using SpEL expressions when needed. 3. **Error Handling**: Always handle permission-related exceptions appropriately. 4. **Permission Granularity**: Design permissions with appropriate granularity for your use case. 5. **Security Context**: Ensure proper security context is available when using default user ID resolution. ## License This project is licensed under the Apache License 2.0 - see the LICENSE file for details.