413 lines
13 KiB
Markdown
413 lines
13 KiB
Markdown
# 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
|
|
<dependency>
|
|
<groupId>de.mumme-it</groupId>
|
|
<artifactId>permission-manager-sdk</artifactId>
|
|
<version>0.1.1</version>
|
|
</dependency>
|
|
```
|
|
|
|
## 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<Integration<?>> getIntegrations() {
|
|
return IntegrationBuilder.create()
|
|
// Create a domain and set it as the current context
|
|
.createDomain("users", "User management domain")
|
|
// Add permissions to the current domain
|
|
.addPermission("read", "Permission to read user data")
|
|
.addPermission("write", "Permission to write user data")
|
|
// Add a role to the current domain
|
|
.addRole("user_viewer", "Role for viewing user data")
|
|
// Assign permissions to the role
|
|
.assignPermissionsToRole("user_viewer", List.of("read"))
|
|
// Create another domain with its permissions and roles
|
|
.createDomain("orders", "Order management domain")
|
|
.addPermission("view", "Permission to view orders")
|
|
.addPermission("create", "Permission to create orders")
|
|
.addRole("order_manager", "Role for managing orders")
|
|
.assignPermissionsToRole("order_manager", List.of("view", "create"))
|
|
.build();
|
|
}
|
|
}
|
|
```
|
|
|
|
The `IntegrationBuilder` provides a fluent API for creating integrations with features like:
|
|
|
|
- Domain context management (automatically tracks the current domain)
|
|
- Method chaining for building complex permission structures
|
|
- Type-safe operations for all integration types
|
|
- Automatic generation of integration IDs
|
|
|
|
Available builder methods:
|
|
- Domain operations:
|
|
- `createDomain(name, description)`
|
|
- `updateDomain(oldName, newName, description)`
|
|
- `deleteDomain(name)`
|
|
- `selectDomain(name)` - Switch context without creating a domain
|
|
- Permission operations:
|
|
- `addPermission(name, description)`
|
|
- `updatePermission(oldName, newName, description)`
|
|
- `removePermission(name)`
|
|
- Role operations:
|
|
- `addRole(name, description)`
|
|
- `updateRole(oldName, newName, description)`
|
|
- `removeRole(name)`
|
|
- Role-Permission relations:
|
|
- `assignPermissionsToRole(role, permissions)`
|
|
|
|
The builder ensures that operations are performed in the correct context by requiring a domain to be selected or created before performing domain-specific operations.
|
|
|
|
The integrations will be performed automatically when your application starts up. You can implement either `getIntegrations()` or `getIntegrationsJsonPath()` - if both are implemented, `getIntegrations()` takes precedence.
|
|
|
|
Available integration types:
|
|
- `DomainIntegration`: Create/update domains
|
|
- `PermissionIntegration`: Create/update permissions within domains
|
|
- `RoleIntegration`: Create/update roles within domains
|
|
- `RolePermissionRelationIntegration`: Manage relationships between roles and permissions
|
|
|
|
Each integration requires:
|
|
- `id`: A unique identifier for the integration
|
|
- `action`: The operation to perform (`create`, `update`, or `delete`)
|
|
- `data`: The entity-specific data for the integration
|
|
|
|
## Usage
|
|
|
|
### 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<User> 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<Report> 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<Permit> 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<Permission> 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<String> 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. |