Skip to content

Latest commit

 

History

History
312 lines (252 loc) · 10.6 KB

File metadata and controls

312 lines (252 loc) · 10.6 KB

EASY-ABAC documentation

Installation and requirements

How to build?

JDK 8+ is required.

mvn clean install 

How to use?

To start working with the Easy-ABAC Framework you need to add the easy-abac-{version}.jar to the classpath/module-path or add it as a maven dependency, like this:

<dependency>
    <groupId>com.exadel.security</groupId>
    <artifactId>easy-abac</artifactId>
    <version>${abac-version}</version>
</dependency>

Framework also requires spring-context dependency for not spring-based projects:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
</dependency>

Import framework configuration using @Import annotation:

@SpringBootApplication
@Import(AbacConfiguration.class)
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

or using @ImportResource annotation:

@SpringBootApplication
@ImportResource("classpath*:abac-config.xml")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Components Easy-ABAC components

Core Attributes

  • Action interface - to define possible actions with entity
  • @Access annotation - to define custom annotation to restrict access to entity
  • @Id annotation - to define entity identifier parameter in method
  • EntityAccessValidator interface - to define access validation rules for entity(s)
  • @ProtectedResource and @PublicResource annotations - to turn on / turn of easy-abac validation respectively

Example

Let's consider simple example: you have resource (entity) Project, CRUD operations with them and would like to restrict access to the resource.

1. Define available actions for your resource. For example:

    public enum ProjectAction implements com.exadel.easyabac.model.core.Action {
        VIEW,
        UPDATE,
        CREATE,
        DELETE
    }

Actions are used to differentiate access rights to the resource, each authenticated user may have different set of available actions. Further will be described have to attach these actions to user.

2.: Create your entity's entityId annotation :

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    public @interface ProjectId {
    }

The @ProjectId annotation will help us define entity identifier parameter in controller or service method:

public ResponseEntity get(@ProjectId @PathVariable("projectId") Long projectId) {...}

3.: Define the annotation which protects your REST resource.

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Access(id = ProjectId.class)
    public @interface ProjectAccess {
  
        /* required actions, see Step 1 */
        ProjectAction[] value();
        
        /* Choose your validator */
        Class<? extends EntityAccessValidator> validator() default ProjectValidator.class;
    }

The @ProjectAccess annotation will be used to protect REST methods. Here you should define type of actions created in Step 1 as well as validator.

4.: Implement the EntityAccessValidator interface for this resource, where the authorization logic is implemented:

    public class ProjectValidator implements com.exadel.easyabac.model.validation.EntityAccessValidator<ProjectAction> {
        @Override
        public void validate(ExecutionContext<ProjectAction> context) {
            //your validation logic here
        }
    }

The validator might be defined as simple class as well as Spring component. If your validator throws any exception, the access to the resource is denied. The ExecutionContext have the following attributes you can use for validation and logging:

  • Entity identifier (Project identifier in our case)
  • Set of actions which are required to access the resource.
  • The action class type.
  • The org.aspectj.lang.JoinPoint which contains more details about protected method.

5.: Protect your REST endpoints using your annotations.

    @RequestMapping("/{projectId}")
    @ProtectedResource
    @ProjectAccess(ProjectAction.VIEW)
    public class ProjectController {
        
        @RequestMapping("/get")
        public Project get(@ProjectId @PathVariable("projectId") Long projectId) {
            // your code here
        }    
        
        @ProjectAccess(ProjectAction.UPDATE)
        @RequestMapping("/update")
        public Project update(@ProjectId @PathVariable("projectId") Long projectId) {
            //your code here
        }    

        @ProjectAccess(ProjectAction.DELETE)
        @RequestMapping("/delete")
        public Project delete(@PathVariable("projectId") Long projectId) {
             //your code here
        }
        
        @PublicResource // turns off EASY-ABAC validation
        public Project close() {
            //your code here
        }
    }

@ProtectedResource is a class-level annotation to turn on easy-abac validation. All public instance methods will be protected unless @ProtectedResource is provided on method, to turn off easy-abac validation.

@ProjectAccess(ProjectAction.VIEW) construction says that only users which have ProjectAction.VIEW action will have access right to the Project.

@ProjectAccess can restricted access as globally when used on class level as locally for particular method. When used both on class and method levels then set of actions are added up together. For example, ProjectController.update requires two actions - ProjectAction.VIEW & ProjectAction.UPDATE.

Compile time checks

The easy-abac provides user-friendly compile time checks:

  • This will raise compile-time error as @ProjectId annotation is missing:
        @ProjectAccess(ProjectAction.DELETE)
        @RequestMapping("/delete")
        public Project delete(@PathVariable("projectId") Long projectId) {
             //your code here
        }
  • This will raise compile-time error as @ProjectAccess annotation is missing while @ProjectId provided:
        @RequestMapping("/delete")
        public Project delete(@ProjectId @PathVariable("projectId") Long projectId) {
             //your code here
        }
  • This will raise compile-time error as @ProjectAccess annotation is missing while resource is marked with @ProtectedResource globally:
    @ProtectedResource
    public class ProjectController {
  
        @RequestMapping("/delete")
        public Project delete(@ProjectId @PathVariable("projectId") Long projectId) {
             //your code here
        }
    }
  • This will raise compile-time error as value() is missing while required:
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Access(id = ProjectId.class)
    public @interface ProjectAccess {
  
        /* required actions, see Step 1 */
        ProjectAction[] value();
        
        /* Choose your validator */
        Class<? extends EntityAccessValidator> validator() default ProjectValidator.class;
    }
  • This will raise compile-time error as validator() is missing while required:
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Access(id = ProjectId.class)
    public @interface ProjectAccess {
  
        /* required actions, see Step 1 */
        ProjectAction[] value();
    }

Custom validator implementation

Let's consider example for validator implementation.

@Component
public class GeneralEntityAccessValidator implements EntityAccessValidator<Action> {

    private static final String ERROR_TEMPLATE = "Access to entity[id=%s] denied.";

    @Autowired
    private ActionProvider actionProvider;

    @Autowired
    private DemoAuthorization authorization;

    @Override
    public void validate(ExecutionContext<Action> context) {
        Long entityId = context.getEntityId();
        Set<Action> availableActions = actionProvider.getAvailableActions(entityId, context.getActionType());
        Set<Action> requiredActions = context.getRequiredActions();

        Set<Action> missingActions = SetUtils.difference(requiredActions, availableActions);
        if (CollectionUtils.isEmpty(missingActions)) {
            return;
        }

        AccessResponse response = new AccessResponse(
                authorization.getLoggedUserRole(),
                entityId,
                missingActions,
                context.getJoinPoint().getSignature().toString()
        );
        throw new AccessException(String.format(ERROR_TEMPLATE, entityId), response);
    }
}

ActionProvider is provider of actions is available for current logged in user. Here we calculate difference between actions available for user and required actions. In case when user missing some required actions - AccessException is thrown. Further you're free to handle this exception. For example using ExceptionHandler.

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

    private static final String ACCESS_DENIED_PAGE = "403.html";

    @ExceptionHandler(AccessException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public String handleAccessDeniedException(AccessException exception, Model model) {
        model.addAttribute(exception.getResponse());
        return ACCESS_DENIED_PAGE;
    }
}

Why actions instead of permissions?

We consider the following concepts:

  • Permissions - static user access rights, which cannot be changed depending on any other facts.
  • Actions - dynamic user access rights, which can be changed depending on any other facts.

Example:

User has ProjectAction.UPDATE, but we need to restrict it depending on some project attributes. For example user should be unable to update projects with status 'closed'. So project action ProjectAction.UPDATE is available only for not 'closed' projects. This can be simply implemented as an action provider, which takes user static actions and then filtering them using some dynamic attributes. This also works for static actions.


You can see the framework in action in easy-abac-demo

Project structure: