2024-11-18 01:04:43 +01:00
2025-03-25 01:37:58 +01:00

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:

<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:

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):

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:

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:

[
  {
    "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:

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:

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:

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:

@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:

@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

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

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:

@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.

Description
No description provided
Readme 8.1 MiB
2025-01-08 02:18:02 +00:00
Languages
Java 99.2%
Shell 0.8%