From 134e3b1c68af5372945caa190a8c0dc10671542a Mon Sep 17 00:00:00 2001 From: Gleb Bondarchuk Date: Wed, 19 Feb 2020 20:24:21 +0300 Subject: [PATCH] Introduce EASY-ABAC framework --- .gitignore | 10 + .whitesource | 8 + LICENSE | 202 +++++++++++ README.md | 330 +++++++++++++++++ easy-abac-demo/.gitignore | 10 + easy-abac-demo/README.md | 9 + easy-abac-demo/pom.xml | 70 ++++ .../exadel/easyabac/demo/DemoApplication.java | 24 ++ .../demo/configuration/SecurityConfig.java | 26 ++ .../demo/controller/ProjectController.java | 52 +++ .../demo/controller/StoryController.java | 44 +++ .../demo/controller/TaskController.java | 50 +++ .../demo/controller/WelcomeController.java | 72 ++++ .../demo/exception/AccessException.java | 28 ++ .../demo/handler/RestExceptionHandler.java | 30 ++ .../authorization/ActionProvider.java | 25 ++ .../authorization/DemoActionProvider.java | 115 ++++++ .../authorization/DemoAuthorization.java | 42 +++ .../authorization/UserGrantedAuthority.java | 39 ++ .../AbstractUserAuthentication.java | 52 +++ .../authentication/AdminAuthentication.java | 38 ++ .../BusinessAnalystAuthentication.java | 38 ++ .../DeveloperAuthentication.java | 37 ++ .../demo/security/model/AccessResponse.java | 73 ++++ .../security/model/project/ProjectAccess.java | 27 ++ .../security/model/project/ProjectAction.java | 15 + .../security/model/project/ProjectId.java | 17 + .../security/model/story/StoryAccess.java | 27 ++ .../security/model/story/StoryAction.java | 14 + .../demo/security/model/story/StoryId.java | 17 + .../demo/security/model/task/TaskAccess.java | 27 ++ .../demo/security/model/task/TaskAction.java | 14 + .../demo/security/model/task/TaskId.java | 17 + .../security/validator/DemoValidator.java | 54 +++ .../src/main/resources/application.properties | 2 + .../src/main/resources/templates/403.html | 45 +++ .../src/main/resources/templates/welcome.html | 263 ++++++++++++++ easy-abac/README.md | 6 + .../abac-annotation-processing/README.md | 3 + easy-abac/abac-annotation-processing/pom.xml | 60 +++ .../processor/AccessAnnotationProcessor.java | 132 +++++++ .../AnnotationValidationProcessor.java | 68 ++++ .../processor/ProtectAccessProcessor.java | 66 ++++ .../base/AbstractAnnotationProcessor.java | 88 +++++ .../utils/AnnotationProcessingUtils.java | 162 +++++++++ .../processor/utils/ElementUtils.java | 88 +++++ easy-abac/abac-aspect/README.md | 5 + easy-abac/abac-aspect/pom.xml | 74 ++++ .../easyabac/aspect/AbacConfiguration.java | 14 + .../exadel/easyabac/aspect/AccessAspect.java | 56 +++ .../exadel/easyabac/aspect/AspectUtils.java | 61 ++++ .../aspect/DefaultExecutionContext.java | 78 ++++ .../aspect/ExecutionContextBuilder.java | 73 ++++ .../main/resources/META-INF/spring.factories | 1 + .../src/main/resources/abac-config.xml | 17 + .../src/main/resources/application.properties | 2 + easy-abac/abac-model/README.md | 3 + easy-abac/abac-model/pom.xml | 29 ++ .../easyabac/model/annotation/Access.java | 37 ++ .../model/annotation/ProtectedResource.java | 17 + .../model/annotation/PublicResource.java | 14 + .../exadel/easyabac/model/core/Action.java | 26 ++ .../AbacAnnotationParseException.java | 19 + .../model/exception/AbacSystemException.java | 19 + .../validation/EntityAccessValidator.java | 20 + .../model/validation/ExecutionContext.java | 62 ++++ easy-abac/pom.xml | 96 +++++ pom.xml | 342 ++++++++++++++++++ 68 files changed, 3701 insertions(+) create mode 100644 .gitignore create mode 100644 .whitesource create mode 100644 LICENSE create mode 100644 README.md create mode 100644 easy-abac-demo/.gitignore create mode 100644 easy-abac-demo/README.md create mode 100644 easy-abac-demo/pom.xml create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/DemoApplication.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/configuration/SecurityConfig.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/ProjectController.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/StoryController.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/TaskController.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/WelcomeController.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/exception/AccessException.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/handler/RestExceptionHandler.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/ActionProvider.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoActionProvider.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoAuthorization.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/UserGrantedAuthority.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AbstractUserAuthentication.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AdminAuthentication.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/BusinessAnalystAuthentication.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/DeveloperAuthentication.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/AccessResponse.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAccess.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAction.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectId.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAccess.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAction.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryId.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAccess.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAction.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskId.java create mode 100644 easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/validator/DemoValidator.java create mode 100644 easy-abac-demo/src/main/resources/application.properties create mode 100644 easy-abac-demo/src/main/resources/templates/403.html create mode 100644 easy-abac-demo/src/main/resources/templates/welcome.html create mode 100644 easy-abac/README.md create mode 100644 easy-abac/abac-annotation-processing/README.md create mode 100644 easy-abac/abac-annotation-processing/pom.xml create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AccessAnnotationProcessor.java create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AnnotationValidationProcessor.java create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/ProtectAccessProcessor.java create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/base/AbstractAnnotationProcessor.java create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/AnnotationProcessingUtils.java create mode 100644 easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/ElementUtils.java create mode 100644 easy-abac/abac-aspect/README.md create mode 100644 easy-abac/abac-aspect/pom.xml create mode 100644 easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AbacConfiguration.java create mode 100644 easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AccessAspect.java create mode 100644 easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AspectUtils.java create mode 100644 easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/DefaultExecutionContext.java create mode 100644 easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/ExecutionContextBuilder.java create mode 100644 easy-abac/abac-aspect/src/main/resources/META-INF/spring.factories create mode 100644 easy-abac/abac-aspect/src/main/resources/abac-config.xml create mode 100644 easy-abac/abac-aspect/src/main/resources/application.properties create mode 100644 easy-abac/abac-model/README.md create mode 100644 easy-abac/abac-model/pom.xml create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/Access.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/ProtectedResource.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/PublicResource.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/core/Action.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacAnnotationParseException.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacSystemException.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/EntityAccessValidator.java create mode 100644 easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/ExecutionContext.java create mode 100644 easy-abac/pom.xml create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f62b82e --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +### Maven ### +target/ + +### IDE ### +.idea +*.iml + +### Eclipse ### +.classpath +.project \ No newline at end of file diff --git a/.whitesource b/.whitesource new file mode 100644 index 0000000..e0aaa3e --- /dev/null +++ b/.whitesource @@ -0,0 +1,8 @@ +{ + "checkRunSettings": { + "vulnerableCheckRunConclusionLevel": "failure" + }, + "issueSettings": { + "minSeverityLevel": "LOW" + } +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d27fb87 --- /dev/null +++ b/README.md @@ -0,0 +1,330 @@ +*** + +
+ EASY-ABAC
+ Activity Based Security Framework for Java™ +
+ +
+ +[![Maven Central](http://maven-badges.herokuapp.com/maven-central/com.exadel.security/easy-abac/badge.svg?style=flat)](http://search.maven.org/#artifactdetails|com.exadel.security|easy-abac|1.0-RC2|) +[![License](https://img.shields.io/github/license/apache/incubator-streampipes.svg)](http://www.apache.org/licenses/LICENSE-2.0) + +
+ +*** + +Latest news +----------- +* 01/03/2020: version 1.0-RC2 is out! + +What is Easy-ABAC Framework? +---------------------------- + +Usually developer teams spend much time and money creating and supporting own complex access-control +architectures, which cannot fully match security expectations. +Fairly often REST resources remain unprotected. It's quite problematic to detect them. +The framework will help to detect and fix that. Application will raise compilation error if any resource remains unprotected. + +The aim of the **Easy-ABAC Framework** is to help you protect your REST resources from unauthorized access. +The framework provides a centralized, externalized authorization management system +with flexible fine-grained access rights configuration in declarative manner. + +When to use? +---------------------------- +- Java **spring-based** web applications +- Multi-tenant applications +- Applications with dynamic access rights +- Applications with fine-grained access rights + +Core features +------------- +- Lightweight library and easy to learn API +- Declarative authorization +- **Compile-time** check of missing authorization of REST resources +- **Compile-time** check of proper configuration +- Built for Spring based applications + + +How it works +------------ + +**How to build?** +``` +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 or add it as +a maven dependency, like this: +```xml + + com.exadel.security + easy-abac + ${abac-version} + +``` + +Framework also requires spring-context dependency for not spring-based projects: +```xml + + org.springframework + spring-context + +``` + +**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: +```java + public enum ProjectAction implements com.exadel.easyabac.model.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 : +```java + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface ProjectId { + } +``` + +The ```@ProjectId``` annotation will help us define entity identifier parameter in controller or service method: +```java +public ResponseEntity get(@ProjectId @PathVariable("projectId") Long projectId) {...} +``` + +**3.**: Define the annotation which protects your REST resource. +```java + @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 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: +```java + public class ProjectValidator implements com.exadel.easyabac.aspect.EntityAccessValidator { + @Override + public void validate(ExecutionContext 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. + +```java + @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: +```java + @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: +```java + @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: +```java + @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: +```java + @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 validator() default ProjectValidator.class; + } +``` + +- This will raise compile-time error as ```validator()``` is missing while required: +```java + @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. + +```java +@Component +public class GeneralEntityAccessValidator implements EntityAccessValidator { + + 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 context) { + Long entityId = context.getEntityId(); + Set availableActions = actionProvider.getAvailableActions(entityId, context.getActionType()); + Set requiredActions = context.getRequiredActions(); + + Set 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```. + +```java +@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](easy-abac-demo/README.md) + +Project structure: +- [easy-abac](easy-abac/README.md) + - [abac-annotation-processing](easy-abac/abac-annotation-processing/README.md) + - [abac-aspect](easy-abac/abac-aspect/README.md) + - [abac-model](easy-abac/abac-model/README.md) +- [easy-abac-demo](easy-abac-demo/README.md) – example \ No newline at end of file diff --git a/easy-abac-demo/.gitignore b/easy-abac-demo/.gitignore new file mode 100644 index 0000000..f62b82e --- /dev/null +++ b/easy-abac-demo/.gitignore @@ -0,0 +1,10 @@ +### Maven ### +target/ + +### IDE ### +.idea +*.iml + +### Eclipse ### +.classpath +.project \ No newline at end of file diff --git a/easy-abac-demo/README.md b/easy-abac-demo/README.md new file mode 100644 index 0000000..330f704 --- /dev/null +++ b/easy-abac-demo/README.md @@ -0,0 +1,9 @@ +# Ease-ABAC in action. + +The Spring Boot application that demonstrates the implementation of the Easy-ABAC Framework + +### How to build and run the web application + +1. Go to the root path of the project and run `mvn clean install` +2. Run `java -jar shop-demo/target/shop-demo-1.0.jar` +3. Go to `http://localhost:8080` diff --git a/easy-abac-demo/pom.xml b/easy-abac-demo/pom.xml new file mode 100644 index 0000000..ef4d738 --- /dev/null +++ b/easy-abac-demo/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.1.4.RELEASE + + + + com.exadel.security + easy-abac-demo + 1.0-RC2 + + easy-abac-demo + easy-abac implementation example + + + 1.8 + + + + + com.exadel.security + easy-abac + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.sonatype.plugins + nexus-staging-maven-plugin + + true + + + + + + diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/DemoApplication.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/DemoApplication.java new file mode 100644 index 0000000..57028bf --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/DemoApplication.java @@ -0,0 +1,24 @@ +package com.exadel.easyabac.demo; + +import com.exadel.easyabac.aspect.AbacConfiguration; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * Demo application. + * Use {@code @ImportResource("classpath*:abac-config.xml")} + * for non-Spring Boot applications. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@SpringBootApplication +@Import(AbacConfiguration.class) +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/configuration/SecurityConfig.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/configuration/SecurityConfig.java new file mode 100644 index 0000000..58710fe --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/configuration/SecurityConfig.java @@ -0,0 +1,26 @@ +package com.exadel.easyabac.demo.configuration; + +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.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +/** + * The application security configuration. + * + * @author Gleb Bondarchuk + * @author Igor Sych + * @since 1.0-RC1 + */ +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests() + .antMatchers("/*", "/welcome", "/login-as-user", "/login-as-administrator").permitAll() + .anyRequest() + .authenticated(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/ProjectController.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/ProjectController.java new file mode 100644 index 0000000..0197bab --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/ProjectController.java @@ -0,0 +1,52 @@ +package com.exadel.easyabac.demo.controller; + +import static com.exadel.easyabac.demo.security.model.project.ProjectAction.DELETE; +import static com.exadel.easyabac.demo.security.model.project.ProjectAction.UPDATE; +import static com.exadel.easyabac.demo.security.model.project.ProjectAction.VIEW; + +import com.exadel.easyabac.demo.security.model.project.ProjectAccess; +import com.exadel.easyabac.demo.security.model.project.ProjectId; +import com.exadel.easyabac.model.annotation.ProtectedResource; +import com.exadel.easyabac.model.annotation.PublicResource; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Sample controller for Project entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@ProtectedResource +@ProjectAccess(VIEW) +@RequestMapping("/projects/{projectId}") +@RestController +public class ProjectController { + + @ProjectAccess(VIEW) + @RequestMapping + public ResponseEntity get(@ProjectId @PathVariable("projectId") Long projectId) { + return ResponseEntity.ok().build(); + } + + @ProjectAccess(UPDATE) + @RequestMapping("/update") + public ResponseEntity update(@ProjectId @PathVariable("projectId") Long projectId) { + return ResponseEntity.ok().build(); + } + + @ProjectAccess(DELETE) + @RequestMapping("/delete") + public ResponseEntity delete(@ProjectId @PathVariable("projectId") Long projectId) { + return ResponseEntity.ok().build(); + } + + @PublicResource + @RequestMapping("/public-info") + public ResponseEntity getPublicInfo() { + return ResponseEntity.ok().build(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/StoryController.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/StoryController.java new file mode 100644 index 0000000..721e03e --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/StoryController.java @@ -0,0 +1,44 @@ +package com.exadel.easyabac.demo.controller; + +import static com.exadel.easyabac.demo.security.model.project.ProjectAction.VIEW; + +import com.exadel.easyabac.demo.security.model.project.ProjectAccess; +import com.exadel.easyabac.demo.security.model.project.ProjectId; +import com.exadel.easyabac.demo.security.model.story.StoryAccess; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.story.StoryId; +import com.exadel.easyabac.model.annotation.ProtectedResource; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Sample controller for Story entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@ProtectedResource +@ProjectAccess(VIEW) +@RequestMapping("projects/{projectId}/stories/{storyId}") +@RestController +public class StoryController { + + @StoryAccess(StoryAction.VIEW) + @RequestMapping + public ResponseEntity get( + @ProjectId @PathVariable("projectId") Long projectId, + @StoryId @PathVariable("storyId") Long storyId) { + return ResponseEntity.ok().build(); + } + + @StoryAccess(StoryAction.UPDATE) + @RequestMapping("/update") + public ResponseEntity update( + @ProjectId @PathVariable("projectId") Long projectId, + @StoryId @PathVariable("storyId") Long storyId) { + return ResponseEntity.ok().build(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/TaskController.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/TaskController.java new file mode 100644 index 0000000..f3844a7 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/TaskController.java @@ -0,0 +1,50 @@ +package com.exadel.easyabac.demo.controller; + +import static com.exadel.easyabac.demo.security.model.project.ProjectAction.VIEW; + +import com.exadel.easyabac.demo.security.model.project.ProjectAccess; +import com.exadel.easyabac.demo.security.model.project.ProjectId; +import com.exadel.easyabac.demo.security.model.story.StoryAccess; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.story.StoryId; +import com.exadel.easyabac.demo.security.model.task.TaskAccess; +import com.exadel.easyabac.demo.security.model.task.TaskAction; +import com.exadel.easyabac.demo.security.model.task.TaskId; +import com.exadel.easyabac.model.annotation.ProtectedResource; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Sample controller for Task entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@ProtectedResource +@ProjectAccess(VIEW) +@StoryAccess(StoryAction.VIEW) +@RequestMapping("projects/{projectId}/stories/{storyId}/tasks/{taskId}") +@RestController +public class TaskController { + + @TaskAccess(TaskAction.VIEW) + @RequestMapping + public ResponseEntity get( + @ProjectId @PathVariable("projectId") Long projectId, + @StoryId @PathVariable("storyId") Long storyId, + @TaskId @PathVariable("taskId") Long taskId) { + return ResponseEntity.ok().build(); + } + + @TaskAccess(TaskAction.UPDATE) + @RequestMapping("/update") + public ResponseEntity update( + @ProjectId @PathVariable("projectId") Long projectId, + @StoryId @PathVariable("storyId") Long storyId, + @TaskId @PathVariable("taskId") Long taskId) { + return ResponseEntity.ok().build(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/WelcomeController.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/WelcomeController.java new file mode 100644 index 0000000..9393db3 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/controller/WelcomeController.java @@ -0,0 +1,72 @@ +package com.exadel.easyabac.demo.controller; + +import com.exadel.easyabac.demo.security.authorization.DemoAuthorization; +import com.exadel.easyabac.demo.security.authorization.authentication.AdminAuthentication; +import com.exadel.easyabac.demo.security.authorization.authentication.BusinessAnalystAuthentication; +import com.exadel.easyabac.demo.security.authorization.authentication.DeveloperAuthentication; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import javax.servlet.http.HttpServletRequest; + + +/** + * Application welcome controller. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Controller +public class WelcomeController { + + private static final String WELCOME_PAGE = "welcome"; + private static final String AUTHORIZED_USER = "authorizedUser"; + private static final String BASE_URL = "baseURL"; + + @Autowired + private DemoAuthorization authorization; + + @GetMapping("/") + public String root(Model model, HttpServletRequest request) { + return getWelcomePage(model, request); + } + + @GetMapping("/logout") + public String logout(Model model, HttpServletRequest request) { + authorization.logout(); + return getWelcomePage(model, request); + } + + @GetMapping("/login-as-admin") + public String loginAsAdministrator(Model model, HttpServletRequest request) { + authorization.loginAs(new AdminAuthentication()); + return getWelcomePage(model, request); + } + + @GetMapping("/login-as-ba") + public String loginAsBusinessAnalyst(Model model, HttpServletRequest request) { + authorization.loginAs(new BusinessAnalystAuthentication()); + return getWelcomePage(model, request); + } + + @GetMapping("/login-as-dev") + public String loginAsDeveloper(Model model, HttpServletRequest request) { + authorization.loginAs(new DeveloperAuthentication()); + return getWelcomePage(model, request); + } + + private String getWelcomePage(Model model, HttpServletRequest request) { + model.addAttribute(AUTHORIZED_USER, authorization.getLoggedUserRole()); + model.addAttribute(BASE_URL, getBaseURL(request)); + return WELCOME_PAGE; + } + + private String getBaseURL(HttpServletRequest request) { + String requestURL = request.getRequestURL().toString(); + return requestURL.substring(0, requestURL.length() - request.getRequestURI().length()) + request.getContextPath(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/exception/AccessException.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/exception/AccessException.java new file mode 100644 index 0000000..01a7158 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/exception/AccessException.java @@ -0,0 +1,28 @@ +package com.exadel.easyabac.demo.exception; + +import com.exadel.easyabac.demo.security.model.AccessResponse; + +/** + * The resource access exception. + * + * @author Igor Sych + * @since 1.0-RC1 + */ +public class AccessException extends RuntimeException { + + private final AccessResponse response; + + /** + * Instantiates a new Access exception. + * + * @param message the message + */ + public AccessException(String message, AccessResponse response) { + super(message); + this.response = response; + } + + public AccessResponse getResponse() { + return response; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/handler/RestExceptionHandler.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/handler/RestExceptionHandler.java new file mode 100644 index 0000000..6ffeb5f --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/handler/RestExceptionHandler.java @@ -0,0 +1,30 @@ +package com.exadel.easyabac.demo.handler; + +import com.exadel.easyabac.demo.exception.AccessException; + +import org.springframework.http.HttpStatus; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * Application exception handler. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@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; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/ActionProvider.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/ActionProvider.java new file mode 100644 index 0000000..829d63c --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/ActionProvider.java @@ -0,0 +1,25 @@ +package com.exadel.easyabac.demo.security.authorization; + +import com.exadel.easyabac.model.core.Action; + +import java.util.Set; + +/** + * Example of entity action provider. + * + * @author Gleb Bondarchuk + * @author Igor Sych + * @since 1.0-RC1 + */ +public interface ActionProvider { + + + /** + * Example of generic method to fetch actions by particular type. + * + * @param entityId the entity identifier + * @param entityClass the entity class + * @return the available actions for entity + */ + Set getAvailableActions(Long entityId, Class entityClass); +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoActionProvider.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoActionProvider.java new file mode 100644 index 0000000..8317896 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoActionProvider.java @@ -0,0 +1,115 @@ +package com.exadel.easyabac.demo.security.authorization; + +import com.exadel.easyabac.demo.security.model.project.ProjectAction; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.task.TaskAction; +import com.exadel.easyabac.model.core.Action; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * The demo actions provider. + * + * @author Gleb Bondarchuk + * @author Igor Sych + * @since 1.0-RC1 + */ +@Component +public class DemoActionProvider implements ActionProvider { + + @Override + public Set getAvailableActions(Long entityId, Class entityClass) { + Set actions = getUserActions(); + + // this is just example of how actions can be restricted by some business dynamic attributes, + // so one entity has the action while other entity of the same type - doesn't. + if (entityClass.isAssignableFrom(ProjectAction.class)) { + return filterProjectActions(actions, entityId); + } + if (entityClass.isAssignableFrom(StoryAction.class)) { + return filterStoryActions(actions, entityId); + } + if (entityClass.isAssignableFrom(TaskAction.class)) { + return filterTaskActions(actions, entityId); + } + throw new IllegalArgumentException("Action " + entityClass.getName() + " is not handled"); + } + + /** + * Filter actions available for user for particular project with {@code projectId} + * + * @param actions the actions available for user + * @param projectId the project identifier + * @return the available project actions + */ + private Set filterProjectActions(Set actions, Long projectId) { + Set projectActions = filterActions(actions, ProjectAction.class); + + // this is example of how actions can be restricted by some project attributes, for example, status, etc. + // so, for example, projects with 'active' status are visible for developers, while others - aren't. + if (projectId == 1L) { + return projectActions; + } else { + return Collections.emptySet(); + } + } + + /** + * Filter actions available for user for particular story with {@code storyId} + * + * @param actions the actions available for user + * @param storyId the story identifier + * @return the available story actions + */ + private Set filterStoryActions(Set actions, Long storyId) { + Set storyActions = filterActions(actions, StoryAction.class); + + // this is example of how actions can be restricted by some story attributes, for example, status, etc. + // so, for example, stories with 'active' status are visible for developers, while others - aren't. + if (storyId == 1L) { + return storyActions; + } else { + return Collections.emptySet(); + } + } + + /** + * Filter actions available for user for particular task with {@code taskId} + * + * @param actions the actions available for user + * @param taskId the task identifier + * @return the available task actions + */ + private Set filterTaskActions(Set actions, Long taskId) { + Set taskActions = filterActions(actions, TaskAction.class); + + // this is example of how actions can be restricted by some task attributes, for example, status, etc. + // so, for example, tasks with 'active' status are visible for developers, while others - aren't. + if (taskId == 1L) { + return taskActions; + } else { + return Collections.emptySet(); + } + } + + private Set getUserActions() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication.getAuthorities().stream() + .map(UserGrantedAuthority.class::cast) + .map(UserGrantedAuthority::getAction) + .collect(Collectors.toSet()); + } + + private Set filterActions(Set actions, Class type) { + return actions.stream().filter(type::isInstance).collect(Collectors.toSet()); + } +} + + + diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoAuthorization.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoAuthorization.java new file mode 100644 index 0000000..43f2eb6 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/DemoAuthorization.java @@ -0,0 +1,42 @@ +package com.exadel.easyabac.demo.security.authorization; + + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.security.Principal; +import java.util.Optional; + +/** + * Demo authorization utility methods. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Component +public class DemoAuthorization { + + public void loginAs(Authentication authentication) { + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + /** + * Logout. + */ + public void logout() { + loginAs(null); + } + + /** + * Gets logged user role. + * + * @return the logged user role + */ + public String getLoggedUserRole() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .map(Principal::getName) + .orElse(null); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/UserGrantedAuthority.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/UserGrantedAuthority.java new file mode 100644 index 0000000..21abf0d --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/UserGrantedAuthority.java @@ -0,0 +1,39 @@ +package com.exadel.easyabac.demo.security.authorization; + +import com.exadel.easyabac.model.core.Action; + +import org.springframework.security.core.GrantedAuthority; + +/** + * User granted authority. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class UserGrantedAuthority implements GrantedAuthority { + + private Action action; + + /** + * Instantiates a new User granted authority. + * + * @param action the action + */ + public UserGrantedAuthority(Action action) { + this.action = action; + } + + /** + * Gets action. + * + * @return the action + */ + public Action getAction() { + return action; + } + + @Override + public String getAuthority() { + return action.name(); + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AbstractUserAuthentication.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AbstractUserAuthentication.java new file mode 100644 index 0000000..d6031a8 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AbstractUserAuthentication.java @@ -0,0 +1,52 @@ +package com.exadel.easyabac.demo.security.authorization.authentication; + + +import com.exadel.easyabac.demo.security.authorization.UserGrantedAuthority; +import com.exadel.easyabac.model.core.Action; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * The example of abstract user authentication. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public abstract class AbstractUserAuthentication implements Authentication { + + protected abstract Set getUserActions(); + + @Override + public Collection getAuthorities() { + return getUserActions().stream().map(UserGrantedAuthority::new).collect(Collectors.toSet()); + } + + @Override + public Object getCredentials() { + return null; + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return null; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean b) { + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AdminAuthentication.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AdminAuthentication.java new file mode 100644 index 0000000..7ab39fc --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/AdminAuthentication.java @@ -0,0 +1,38 @@ +package com.exadel.easyabac.demo.security.authorization.authentication; + +import com.google.common.collect.ImmutableSet; + +import com.exadel.easyabac.demo.security.model.project.ProjectAction; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.task.TaskAction; +import com.exadel.easyabac.model.core.Action; + +import java.util.Set; + +/** + * Admin user authentication. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class AdminAuthentication extends AbstractUserAuthentication { + + private static final Set AVAILABLE_ACTIONS = ImmutableSet.of( + ProjectAction.VIEW, + ProjectAction.UPDATE, + ProjectAction.DELETE, + + StoryAction.VIEW, + TaskAction.VIEW + ); + + @Override + protected Set getUserActions() { + return AVAILABLE_ACTIONS; + } + + @Override + public String getName() { + return "Administrator"; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/BusinessAnalystAuthentication.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/BusinessAnalystAuthentication.java new file mode 100644 index 0000000..c84a845 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/BusinessAnalystAuthentication.java @@ -0,0 +1,38 @@ +package com.exadel.easyabac.demo.security.authorization.authentication; + +import com.google.common.collect.ImmutableSet; + +import com.exadel.easyabac.demo.security.model.project.ProjectAction; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.task.TaskAction; +import com.exadel.easyabac.model.core.Action; + +import java.util.Set; + +/** + * Business Analyst user authentication. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class BusinessAnalystAuthentication extends AbstractUserAuthentication { + + private static final Set AVAILABLE_ACTIONS = ImmutableSet.of( + ProjectAction.VIEW, + + StoryAction.VIEW, + StoryAction.UPDATE, + TaskAction.VIEW, + TaskAction.UPDATE + ); + + @Override + protected Set getUserActions() { + return AVAILABLE_ACTIONS; + } + + @Override + public String getName() { + return "Business Analyst"; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/DeveloperAuthentication.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/DeveloperAuthentication.java new file mode 100644 index 0000000..6a1d8ce --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/authorization/authentication/DeveloperAuthentication.java @@ -0,0 +1,37 @@ +package com.exadel.easyabac.demo.security.authorization.authentication; + +import com.google.common.collect.ImmutableSet; + +import com.exadel.easyabac.demo.security.model.project.ProjectAction; +import com.exadel.easyabac.demo.security.model.story.StoryAction; +import com.exadel.easyabac.demo.security.model.task.TaskAction; +import com.exadel.easyabac.model.core.Action; + +import java.util.Set; + +/** + * Developer user authentication. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class DeveloperAuthentication extends AbstractUserAuthentication { + + private static final Set AVAILABLE_ACTIONS = ImmutableSet.of( + ProjectAction.VIEW, + + StoryAction.VIEW, + TaskAction.VIEW, + TaskAction.UPDATE + ); + + @Override + protected Set getUserActions() { + return AVAILABLE_ACTIONS; + } + + @Override + public String getName() { + return "Developer"; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/AccessResponse.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/AccessResponse.java new file mode 100644 index 0000000..a3331d5 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/AccessResponse.java @@ -0,0 +1,73 @@ +package com.exadel.easyabac.demo.security.model; + +import com.exadel.easyabac.model.core.Action; + +import java.io.Serializable; +import java.util.Set; + +/** + * Example of response saying user that some required actions to execute the REST are missing. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class AccessResponse implements Serializable { + + private static final long serialVersionUID = 5930916869380046460L; + + private String userRole; + private Long entityId; + private Set missingActions; + private String resource; + + /** + * Instantiates a new Access response. + * + * @param userRole the user role + * @param entityId the entity id + * @param missingActions the missing actions + * @param resource the resource + */ + public AccessResponse(String userRole, Long entityId, Set missingActions, String resource) { + this.userRole = userRole; + this.entityId = entityId; + this.missingActions = missingActions; + this.resource = resource; + } + + /** + * Gets user role. + * + * @return the user role + */ + public String getUserRole() { + return userRole; + } + + /** + * Gets entity id. + * + * @return the entity id + */ + public Long getEntityId() { + return entityId; + } + + /** + * Gets missing actions. + * + * @return the missing actions + */ + public Set getMissingActions() { + return missingActions; + } + + /** + * Gets resource. + * + * @return the resource + */ + public String getResource() { + return resource; + } +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAccess.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAccess.java new file mode 100644 index 0000000..1dd070c --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAccess.java @@ -0,0 +1,27 @@ +package com.exadel.easyabac.demo.security.model.project; + +import com.exadel.easyabac.demo.security.validator.DemoValidator; +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.validation.EntityAccessValidator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Example of ABAC annotation to restrict project entity access. + * This annotation will be placed to controllers or services to enable protection. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +@Access(identifier = ProjectId.class) +public @interface ProjectAccess { + + ProjectAction[] value(); + + Class validator() default DemoValidator.class; +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAction.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAction.java new file mode 100644 index 0000000..b6f3267 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectAction.java @@ -0,0 +1,15 @@ +package com.exadel.easyabac.demo.security.model.project; + +import com.exadel.easyabac.model.core.Action; + +/** + * Here you define your actions to restrict access to Project entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public enum ProjectAction implements Action { + VIEW, + UPDATE, + DELETE +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectId.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectId.java new file mode 100644 index 0000000..8ccc68a --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/project/ProjectId.java @@ -0,0 +1,17 @@ +package com.exadel.easyabac.demo.security.model.project; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Id-type annotation implementation related to {@code ProjectAccess} annotation. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface ProjectId { +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAccess.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAccess.java new file mode 100644 index 0000000..cd72428 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAccess.java @@ -0,0 +1,27 @@ +package com.exadel.easyabac.demo.security.model.story; + +import com.exadel.easyabac.demo.security.validator.DemoValidator; +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.validation.EntityAccessValidator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Example of ABAC annotation to restrict story entity access. + * This annotation will be placed to controllers or services to enable protection. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +@Access(identifier = StoryId.class) +public @interface StoryAccess { + + StoryAction[] value(); + + Class validator() default DemoValidator.class; +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAction.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAction.java new file mode 100644 index 0000000..9771000 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryAction.java @@ -0,0 +1,14 @@ +package com.exadel.easyabac.demo.security.model.story; + +import com.exadel.easyabac.model.core.Action; + +/** + * Here you define your actions to restrict access to Story entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public enum StoryAction implements Action { + VIEW, + UPDATE +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryId.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryId.java new file mode 100644 index 0000000..aecb19c --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/story/StoryId.java @@ -0,0 +1,17 @@ +package com.exadel.easyabac.demo.security.model.story; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Id-type annotation implementation related to {@code StoryAccess} annotation. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface StoryId { +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAccess.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAccess.java new file mode 100644 index 0000000..5e479b4 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAccess.java @@ -0,0 +1,27 @@ +package com.exadel.easyabac.demo.security.model.task; + +import com.exadel.easyabac.demo.security.validator.DemoValidator; +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.validation.EntityAccessValidator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Example of ABAC annotation to restrict task entity access. + * This annotation will be placed to controllers or services to enable protection. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +@Access(identifier = TaskId.class) +public @interface TaskAccess { + + TaskAction[] value(); + + Class validator() default DemoValidator.class; +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAction.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAction.java new file mode 100644 index 0000000..f842915 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskAction.java @@ -0,0 +1,14 @@ +package com.exadel.easyabac.demo.security.model.task; + +import com.exadel.easyabac.model.core.Action; + +/** + * Here you define your actions to restrict access to Task entity. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public enum TaskAction implements Action { + VIEW, + UPDATE +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskId.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskId.java new file mode 100644 index 0000000..8e21e77 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/model/task/TaskId.java @@ -0,0 +1,17 @@ +package com.exadel.easyabac.demo.security.model.task; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Id-type annotation implementation related to {@code TaskAccess} annotation. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface TaskId { +} diff --git a/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/validator/DemoValidator.java b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/validator/DemoValidator.java new file mode 100644 index 0000000..db87ec9 --- /dev/null +++ b/easy-abac-demo/src/main/java/com/exadel/easyabac/demo/security/validator/DemoValidator.java @@ -0,0 +1,54 @@ +package com.exadel.easyabac.demo.security.validator; + +import com.exadel.easyabac.demo.exception.AccessException; +import com.exadel.easyabac.demo.security.authorization.ActionProvider; +import com.exadel.easyabac.demo.security.authorization.DemoAuthorization; +import com.exadel.easyabac.demo.security.model.AccessResponse; +import com.exadel.easyabac.model.core.Action; +import com.exadel.easyabac.model.validation.EntityAccessValidator; +import com.exadel.easyabac.model.validation.ExecutionContext; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.SetUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Set; + +/** + * General-purpose validator example. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Component +public class DemoValidator implements EntityAccessValidator { + + 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 context) { + Long entityId = context.getEntityId(); + Set availableActions = actionProvider.getAvailableActions(entityId, context.getActionType()); + Set requiredActions = context.getRequiredActions(); + + Set 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); + } +} diff --git a/easy-abac-demo/src/main/resources/application.properties b/easy-abac-demo/src/main/resources/application.properties new file mode 100644 index 0000000..c49b26e --- /dev/null +++ b/easy-abac-demo/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.org.springframework.aop=debug +logging.level.com.exadel=trace \ No newline at end of file diff --git a/easy-abac-demo/src/main/resources/templates/403.html b/easy-abac-demo/src/main/resources/templates/403.html new file mode 100644 index 0000000..34417c5 --- /dev/null +++ b/easy-abac-demo/src/main/resources/templates/403.html @@ -0,0 +1,45 @@ + + + + + +

Access to the resource was denied by Easy-ABAC Framework

+

HTTP ERROR 403

+ + + + + + + + + + + + + + + + + + +
User Role
Entity Identifier
Resource
Missing Actions
+
+Return to the Home Page + + \ No newline at end of file diff --git a/easy-abac-demo/src/main/resources/templates/welcome.html b/easy-abac-demo/src/main/resources/templates/welcome.html new file mode 100644 index 0000000..9192198 --- /dev/null +++ b/easy-abac-demo/src/main/resources/templates/welcome.html @@ -0,0 +1,263 @@ + + + + + Easy-ABAC usage sample + + + + +

+ The example of using Easy-ABAC Framework +

+

+ Web application that demonstrates the use of Easy-ABAC Framework for managing access to RESTful service. + Returns HTTP Error 403 if access is denied. +

+
+ We take a simplified JIRA model as an example. + There're 3 entity types: +
    +
  • Project
  • +
  • Story
  • +
  • Task
  • +
+ Also, there're 3 types of users which operate entities: +
    +
  • Administrator - has full access to projects, but read-only access to stories and tasks.
  • +
  • Business Analyst - has full access to stories and tasks, but read-only access to projects.
  • +
  • Developer - has full access to tasks, but read-only access to projects and stories.
  • +
+
+
+ Below are structure of entities with identifiers we have and their access rights: +
    +
  • + Project[id=1] +
      +
    • + Story[id=1] +
        +
      • Task[id=1]
      • +
      • Task[id=2]
      • +
      +
    • +
    • + Story[id=2] +
        +
      • Task[id=3]
      • +
      • Task[id=4]
      • +
      +
    • +
    +
  • +
  • + Project[id=2] +
      +
    • + Story[id=3] +
        +
      • Task[id=5]
      • +
      • Task[id=6]
      • +
      +
    • +
    • + Story[id=4] +
        +
      • Task[id=7]
      • +
      • Task[id=8]
      • +
      +
    • +
    +
  • +
+ +
    +
  • Administrator has access to both Project[id=1] and all their stories and tasks.
  • +
  • Business Analyst has access to Project[id=1], Story[id=1] and all their tasks.
  • +
  • Developer has access to Project[id=1], Story[id=1] and Task[id=1].
  • +
+
+
+ + You're logged as Administrator. + + + You're logged as Business Analyst. + + + You're logged as Developer. + + + You are not authorized. Select one of the authorization options: + +
+ + +

Test cases for calling REST service endpoints:

+

Shop

+ +
    +
  • + + +
    + + +
    + + +
    +
    +
      +
    • + + +
      + + +
      +
      +
        +
      • + + +
        + + +
        +
        +
      • +
      • + + +
        + + +
        +
        +
      • +
      +
    • +
    • + + +
      + + +
      +
      +
        +
      • + + +
        + + +
        +
        +
      • +
      • + + +
        + + +
        +
        +
      • +
      +
    • +
    +
  • +
  • + + +
    + + +
    + + +
    +
    +
      +
    • + + +
      + + +
      +
      +
        +
      • + + +
        + + +
        +
        +
      • +
      • + + +
        + + +
        +
        +
      • +
      +
    • +
    • + + +
      + + +
      +
      +
        +
      • + + +
        + + +
        +
        +
      • +
      • + + +
        + + +
        +
        +
      • +
      +
    • +
    +
  • +
+ + + diff --git a/easy-abac/README.md b/easy-abac/README.md new file mode 100644 index 0000000..3777c96 --- /dev/null +++ b/easy-abac/README.md @@ -0,0 +1,6 @@ +## Easy-ABAC Framework + +Collects all components of the framework into one. + + + \ No newline at end of file diff --git a/easy-abac/abac-annotation-processing/README.md b/easy-abac/abac-annotation-processing/README.md new file mode 100644 index 0000000..29214c2 --- /dev/null +++ b/easy-abac/abac-annotation-processing/README.md @@ -0,0 +1,3 @@ +### abac-annotation-processing + +Checks the correct use of the framework on the client side at compile time \ No newline at end of file diff --git a/easy-abac/abac-annotation-processing/pom.xml b/easy-abac/abac-annotation-processing/pom.xml new file mode 100644 index 0000000..3a984d5 --- /dev/null +++ b/easy-abac/abac-annotation-processing/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + + ../.. + com.exadel.security + abac-root + 1.0-RC2 + + + abac-annotation-processing + jar + + + + ${project.groupId} + abac-model + + + + com.google.auto.service + auto-service + + + + org.springframework + spring-context + provided + + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + + com.google.guava + guava + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + + + + \ No newline at end of file diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AccessAnnotationProcessor.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AccessAnnotationProcessor.java new file mode 100644 index 0000000..1475714 --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AccessAnnotationProcessor.java @@ -0,0 +1,132 @@ +package com.exadel.easyabac.processor; + +import static com.exadel.easyabac.processor.utils.AnnotationProcessingUtils.getAnnotationParameterMirror; +import static com.exadel.easyabac.processor.utils.AnnotationProcessingUtils.isAnnotationPresent; +import static com.exadel.easyabac.processor.utils.AnnotationProcessingUtils.isAnnotationPresentOnAnyMethodParameter; +import static com.exadel.easyabac.processor.utils.AnnotationProcessingUtils.isAnnotationPresentOnMethodOrEnclosingClass; +import static com.google.common.collect.ImmutableSet.of; + +import com.google.auto.service.AutoService; + +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.annotation.PublicResource; +import com.exadel.easyabac.processor.base.AbstractAnnotationProcessor; +import com.exadel.easyabac.processor.utils.AnnotationProcessingUtils; +import com.exadel.easyabac.processor.utils.ElementUtils; + +import org.apache.commons.lang3.tuple.Pair; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** + * Annotation processor aimed to check elements annotated with {@code @Access} in conjunction with identifier-annotations. + * Checks whether each method annotated with custom {@code @Access} have a method parameter annotation with identifier-annotation. + * Checks whether each method with parameter annotated with identifier-annotation is annotated with custom {@code @Access}. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@AutoService(Processor.class) +public class AccessAnnotationProcessor extends AbstractAnnotationProcessor { + + private static final String ID_ANNOTATION_MISSING_ERROR = "Methods annotated with @%s must have any parameter annotated with @%s."; + private static final String ACCESS_ANNOTATION_MISSING_ERROR = "Methods with any parameter annotated with @%s must be annotated with @%s."; + + @Override + protected Set> getSupportedAnnotations() { + return of(Access.class); + } + + @Override + protected void process(RoundEnvironment env) { + Set annotations = env.getElementsAnnotatedWith(Access.class); + + /* Get mapping between id and access annotations. */ + List> mapping = getAnnotationsMapping(annotations); + + /* Get methods annotated with access annotation. */ + List methodsWithAccessAnnotation = getMethodsAnnotatedWithAccessAnnotation(mapping, env); + + /* Get methods annotated with id annotation. */ + List methodsWithIdAnnotation = getMethodsAnnotatedWithIdAnnotation(mapping, env); + + /* Process found annotations. */ + for (Pair pair : mapping) { + TypeElement accessAnnotation = pair.getKey(); + TypeElement idAnnotation = pair.getValue(); + + Name accessAnnotationName = accessAnnotation.getQualifiedName(); + Name isAnnotationName = idAnnotation.getQualifiedName(); + + String idAnnotationMissingError = String.format(ID_ANNOTATION_MISSING_ERROR, accessAnnotationName, isAnnotationName); + String accessAnnotationMissingError = String.format(ACCESS_ANNOTATION_MISSING_ERROR, isAnnotationName, accessAnnotationName); + + /* Find methods annotated with access annotation, but with missing id annotation, and raise compilation error. */ + methodsWithAccessAnnotation.stream() + .filter(method -> isAnnotationPresentOnMethodOrEnclosingClass(accessAnnotation, method)) + .filter(method -> !isAnnotationPresentOnAnyMethodParameter(idAnnotation, method)) + .forEach(method -> printErrorMessage(idAnnotationMissingError, method)); + + /* Find methods annotated with id annotation, but with missing access annotation, and raise compilation error. */ + methodsWithIdAnnotation.stream() + .filter(method -> isAnnotationPresentOnAnyMethodParameter(idAnnotation, method)) + .filter(method -> !isAnnotationPresentOnMethodOrEnclosingClass(accessAnnotation, method)) + .forEach(method -> printErrorMessage(accessAnnotationMissingError, method)); + } + } + + private List getMethodsAnnotatedWithAccessAnnotation(List> mapping, RoundEnvironment env) { + List accessAnnotations = mapping.stream().map(Pair::getKey).collect(Collectors.toList()); + return accessAnnotations.stream() + .map(env::getElementsAnnotatedWith) + .flatMap(Collection::stream) + .map(AnnotationProcessingUtils::getMethods) + .flatMap(Collection::stream) + .filter(ElementUtils::isPublicInstanceMethod) + .map(ExecutableElement.class::cast) + .filter(this::isMethodProtected) + .collect(Collectors.toList()); + } + + private List getMethodsAnnotatedWithIdAnnotation(List> mapping, RoundEnvironment env) { + List idAnnotations = mapping.stream().map(Pair::getValue).collect(Collectors.toList()); + return idAnnotations.stream() + .map(env::getElementsAnnotatedWith) + .flatMap(Collection::stream) + .filter(ElementUtils::isParameter) + .map(Element::getEnclosingElement) + .filter(ElementUtils::isPublicInstanceMethod) + .map(ExecutableElement.class::cast) + .filter(this::isMethodProtected) + .collect(Collectors.toList()); + } + + private List> getAnnotationsMapping(Set elements) { + List annotations = elements.stream().filter(ElementUtils::isAnnotation).map(TypeElement.class::cast).collect(Collectors.toList()); + return annotations.stream().map(this::getAnnotationMapping).collect(Collectors.toList()); + } + + private Pair getAnnotationMapping(TypeElement annotation) { + Access access = annotation.getAnnotation(Access.class); + TypeMirror mirror = getAnnotationParameterMirror(access, Access::identifier); + TypeElement idType = (TypeElement) processingEnv.getTypeUtils().asElement(mirror); + return Pair.of(annotation, idType); + } + + private boolean isMethodProtected(ExecutableElement element) { + return !isAnnotationPresent(PublicResource.class, element); + } +} diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AnnotationValidationProcessor.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AnnotationValidationProcessor.java new file mode 100644 index 0000000..5047f7d --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/AnnotationValidationProcessor.java @@ -0,0 +1,68 @@ +package com.exadel.easyabac.processor; + +import static com.google.common.collect.ImmutableSet.of; + +import com.google.auto.service.AutoService; + +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.processor.base.AbstractAnnotationProcessor; +import com.exadel.easyabac.processor.utils.ElementUtils; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; + +/** + * Annotation processor aimed to validate that annotations marked as {@code Access} have right structure. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@AutoService(Processor.class) +public class AnnotationValidationProcessor extends AbstractAnnotationProcessor { + + private static final String METHOD_MISSING_ERROR = "%s() method is missing for @%s"; + + @Override + protected Set> getSupportedAnnotations() { + return of(Access.class); + } + + @Override + protected void process(RoundEnvironment env) { + Set elements = env.getElementsAnnotatedWith(Access.class); + elements.stream().map(TypeElement.class::cast).forEach(this::validate); + } + + private void validate(TypeElement annotation) { + List elements = processingEnv.getElementUtils().getAllMembers(annotation); + + List methods = elements.stream() + .filter(ElementUtils::isMethod) + .map(ExecutableElement.class::cast) + .collect(Collectors.toList()); + + validateMethodPresentOnAnnotation(annotation, methods, Access.ACTIONS_FIELD_NAME, TypeKind.ARRAY); + validateMethodPresentOnAnnotation(annotation, methods, Access.VALIDATOR_FIELD_NAME, TypeKind.DECLARED); + } + + private void validateMethodPresentOnAnnotation(TypeElement annotation, List methods, String methodName, TypeKind methodReturnType) { + Optional valueMethod = methods.stream() + .filter(method -> methodName.equals(method.getSimpleName().toString())) + .filter(method -> method.getReturnType().getKind() == methodReturnType) + .findFirst(); + + if (!valueMethod.isPresent()) { + printErrorMessage(String.format(METHOD_MISSING_ERROR, methodName, annotation), annotation); + } + } +} diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/ProtectAccessProcessor.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/ProtectAccessProcessor.java new file mode 100644 index 0000000..cdb099b --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/ProtectAccessProcessor.java @@ -0,0 +1,66 @@ +package com.exadel.easyabac.processor; + + +import static com.exadel.easyabac.processor.utils.AnnotationProcessingUtils.*; +import static com.google.common.collect.ImmutableSet.of; + +import com.google.auto.service.AutoService; + +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.annotation.ProtectedResource; +import com.exadel.easyabac.model.annotation.PublicResource; +import com.exadel.easyabac.processor.base.AbstractAnnotationProcessor; +import com.exadel.easyabac.processor.utils.AnnotationProcessingUtils; +import com.exadel.easyabac.processor.utils.ElementUtils; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + + +/** + * Annotation processor aimed to check that each element annotated with {@code @ProtectedResource} + * and not annotated with {@code @PublicResource} is annotated with at least one type which specifies access to the method. + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@AutoService(Processor.class) +public class ProtectAccessProcessor extends AbstractAnnotationProcessor { + + private static final String ERROR_MESSAGE = "Class annotated with @" + ProtectedResource.class.getName() + + " should have each instance methods either annotated with @" + PublicResource.class.getName() + + " or annotated with at least one type which specifies access to the method (see @" + Access.class.getName() + " annotation)."; + + @Override + protected Set> getSupportedAnnotations() { + return of(ProtectedResource.class, PublicResource.class, Access.class); + } + + @Override + protected void process(RoundEnvironment env) { + Set elements = env.getElementsAnnotatedWith(ProtectedResource.class); + + Set methods = elements.stream() + .filter(ElementUtils::isClass) + .map(TypeElement.class::cast) + .map(AnnotationProcessingUtils::getPublicInstanceMethods) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + Set invalidMethods = methods.stream() + .filter(method -> !isAnnotationPresent(PublicResource.class, method)) + .filter(method -> !isAnnotationPresentOnMethodOrEnclosingClass(Access.class, method)) + .collect(Collectors.toSet()); + + invalidMethods.forEach(method -> printErrorMessage(ERROR_MESSAGE, method)); + } +} diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/base/AbstractAnnotationProcessor.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/base/AbstractAnnotationProcessor.java new file mode 100644 index 0000000..83f1afb --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/base/AbstractAnnotationProcessor.java @@ -0,0 +1,88 @@ +package com.exadel.easyabac.processor.base; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.lang.annotation.Annotation; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +/** + * Abstract annotation processor. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public abstract class AbstractAnnotationProcessor extends AbstractProcessor { + + /** + * Get supported annotations class by the processor. + * + * @return the set of supported annotation types + */ + protected abstract Set> getSupportedAnnotations(); + + /** + * Run processing on the {@code env}. + * + * @param env the env + */ + protected abstract void process(RoundEnvironment env); + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public Set getSupportedAnnotationTypes() { + return getSupportedAnnotations().stream() + .map(Class::getCanonicalName) + .collect(Collectors.toSet()); + } + + /** + * Prints error message on the specified element. + * + * @param message message to print + * @param element Element on which the message is bound. + */ + protected void printErrorMessage(String message, Element element) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element); + } + + /** + * Prints error message. + * + * @param message message to print + */ + private void printErrorMessage(String message) { + printErrorMessage(message, null); + } + + /** + * Process the annotations against round environment {@code env}. + * Raises the compilation error in case of any exception. + * + * @param annotations the annotations + * @param env the env + * @return the boolean + */ + @Override + public final boolean process(Set annotations, RoundEnvironment env) { + try { + process(env); + } catch (Exception e) { + String message = ExceptionUtils.getStackTrace(e); + printErrorMessage(message); + return true; + } + return false; + } +} diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/AnnotationProcessingUtils.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/AnnotationProcessingUtils.java new file mode 100644 index 0000000..b3dba9b --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/AnnotationProcessingUtils.java @@ -0,0 +1,162 @@ +package com.exadel.easyabac.processor.utils; + +import org.apache.commons.collections4.CollectionUtils; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.TypeMirror; + +/** + * Annotation processing utility methods. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class AnnotationProcessingUtils { + + private AnnotationProcessingUtils() { + //prevent instantiation + } + + /** + * Gets public instance methods. + * + * @param clazz the clazz + * @return the public instance methods + */ + public static Set getPublicInstanceMethods(TypeElement clazz) { + if (clazz.getKind() != ElementKind.CLASS) { + return Collections.emptySet(); + } + + List elements = filterPublicInstanceMethods(clazz.getEnclosedElements()); + return elements.stream() + .map(ExecutableElement.class::cast) + .collect(Collectors.toSet()); + } + + private static List filterPublicInstanceMethods(List elements) { + return elements.stream() + .filter(element -> element.getKind() == ElementKind.METHOD) + .filter(element -> element.getModifiers().contains(Modifier.PUBLIC)) + .filter(element -> !element.getModifiers().contains(Modifier.STATIC)) + .collect(Collectors.toList()); + + } + + /** + * Gets annotation parameter mirror. + * + * @param the type parameter + * @param annotation the annotation + * @param method the method + * @return the annotation parameter mirror + */ + public static TypeMirror getAnnotationParameterMirror(T annotation, Function method) { + try { + method.apply(annotation); + } catch (MirroredTypeException e) { + return e.getTypeMirror(); + } + return null; + } + + /** + * Gets methods. + * + * @param element the element + * @return the methods + */ + public static List getMethods(Element element) { + if (ElementUtils.isMethod(element)) { + return Collections.singletonList(element); + } + if (ElementUtils.isClass(element)) { + return element.getEnclosedElements().stream().filter(ElementUtils::isMethod).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + /** + * Is annotation present boolean. + * + * @param annotation the annotation + * @param element the element + * @return the boolean + */ + public static boolean isAnnotationPresent(Class annotation, Element element) { + //annotation present directly on element + if (element.getAnnotation(annotation) != null) { + return true; + } + //check whether annotation present on another annotation the element is annotated with + return element.getAnnotationMirrors().stream() + .map(AnnotationMirror::getAnnotationType) + .map(DeclaredType::asElement) + .map(type -> type.getAnnotation(annotation)) + .anyMatch(Objects::nonNull); + } + + public static boolean isAnnotationPresentOnMethodOrEnclosingClass(Class annotation, ExecutableElement method) { + return isAnnotationPresent(annotation, method) || isAnnotationPresent(annotation, method.getEnclosingElement()); + } + + /** + * Is annotation present boolean. + * + * @param annotation the annotation + * @param element the element + * @return the boolean + */ + public static boolean isAnnotationPresent(TypeElement annotation, Element element) { + return element.getAnnotationMirrors().stream().anyMatch(mirror -> mirror.getAnnotationType().equals(annotation.asType())); + } + + /** + * Is annotation present on any boolean. + * + * @param annotation the annotation + * @param elements the elements + * @return the boolean + */ + private static boolean isAnnotationPresentOnAny(TypeElement annotation, List elements) { + return CollectionUtils.isNotEmpty(elements) && elements.stream().anyMatch(element -> isAnnotationPresent(annotation, element)); + } + + /** + * Is annotation present on method or enclosing class boolean. + * + * @param annotation the annotation + * @param method the method + * @return the boolean + */ + public static boolean isAnnotationPresentOnMethodOrEnclosingClass(TypeElement annotation, ExecutableElement method) { + return isAnnotationPresent(annotation, method) || isAnnotationPresent(annotation, method.getEnclosingElement()); + } + + /** + * Is annotation present on any method parameter boolean. + * + * @param annotation the annotation + * @param method the method + * @return the boolean + */ + public static boolean isAnnotationPresentOnAnyMethodParameter(TypeElement annotation, ExecutableElement method) { + List parameters = method.getParameters(); + return isAnnotationPresentOnAny(annotation, parameters); + } +} diff --git a/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/ElementUtils.java b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/ElementUtils.java new file mode 100644 index 0000000..ca265e0 --- /dev/null +++ b/easy-abac/abac-annotation-processing/src/main/java/com/exadel/easyabac/processor/utils/ElementUtils.java @@ -0,0 +1,88 @@ +package com.exadel.easyabac.processor.utils; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Modifier; + +/** + * Utility methods for {@code Element} for simplify usage in stream API. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class ElementUtils { + + private ElementUtils() { + //prevent instantiation + } + + /** + * Checks whether element is method. + * + * @param element the element + * @return the boolean + */ + public static boolean isMethod(Element element) { + return element.getKind() == ElementKind.METHOD; + } + + /** + * Checks whether element is class. + * + * @param element the element + * @return the boolean + */ + public static boolean isClass(Element element) { + return element.getKind() == ElementKind.CLASS; + } + + /** + * Checks whether element is annotation. + * + * @param element the element + * @return the boolean + */ + public static boolean isAnnotation(Element element) { + return element.getKind() == ElementKind.ANNOTATION_TYPE; + } + + /** + * Checks whether element is parameter. + * + * @param element the element + * @return the boolean + */ + public static boolean isParameter(Element element) { + return element.getKind() == ElementKind.PARAMETER; + } + + /** + * Checks whether element is public. + * + * @param element the element + * @return the boolean + */ + private static boolean isPublic(Element element) { + return element.getModifiers().contains(Modifier.PUBLIC); + } + + /** + * Checks whether element is static. + * + * @param element the element + * @return the boolean + */ + private static boolean isStatic(Element element) { + return element.getModifiers().contains(Modifier.STATIC); + } + + /** + * Checks whether element is method, public and non-static. + * + * @param element the element + * @return the boolean + */ + public static boolean isPublicInstanceMethod(Element element) { + return isMethod(element) && isPublic(element) && !isStatic(element); + } +} diff --git a/easy-abac/abac-aspect/README.md b/easy-abac/abac-aspect/README.md new file mode 100644 index 0000000..ea81f6d --- /dev/null +++ b/easy-abac/abac-aspect/README.md @@ -0,0 +1,5 @@ +### Easy-abac-aspect + +This is the main part of the framework. Implements a mechanism for protecting class methods using custom annotations. + + diff --git a/easy-abac/abac-aspect/pom.xml b/easy-abac/abac-aspect/pom.xml new file mode 100644 index 0000000..8bd8de0 --- /dev/null +++ b/easy-abac/abac-aspect/pom.xml @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + ../.. + com.exadel.security + abac-root + 1.0-RC2 + + + abac-aspect + jar + + + + ${project.groupId} + abac-model + + + + org.aspectj + aspectjrt + + + org.aspectj + aspectjweaver + + + + + org.springframework + spring-context + provided + + + org.springframework + spring-web + provided + + + org.slf4j + slf4j-api + + + + junit + junit + test + + + + org.springframework + spring-test + test + + + + ch.qos.logback + logback-classic + test + + + + org.apache.commons + commons-collections4 + + + + + \ No newline at end of file diff --git a/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AbacConfiguration.java b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AbacConfiguration.java new file mode 100644 index 0000000..7272028 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AbacConfiguration.java @@ -0,0 +1,14 @@ +package com.exadel.easyabac.aspect; + +import org.springframework.context.annotation.ImportResource; + +/** + * The configuration class for Spring Boot applications. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@org.springframework.context.annotation.Configuration +@ImportResource("classpath*:abac-config.xml") +public class AbacConfiguration { +} diff --git a/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AccessAspect.java b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AccessAspect.java new file mode 100644 index 0000000..b09b457 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AccessAspect.java @@ -0,0 +1,56 @@ +package com.exadel.easyabac.aspect; + + +import com.exadel.easyabac.model.exception.AbacAnnotationParseException; +import com.exadel.easyabac.model.validation.ExecutionContext; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +/** + * Applies access restriction on methods marked by custom Access annotations. + * Process all annotations with nested annotation {@code Access}. + * Depends on particular {@code AccessValidator} implementation. + * + * @author Gleb Bondarchuk + * @author Igor Sych + * @since 1.0-RC1 + */ +@Aspect +class AccessAspect { + + @Autowired + private ExecutionContextBuilder contextBuilder; + + @Pointcut("execution(@(@com.exadel.easyabac.model.annotation.Access *) * *(..) ) ") + public void methodAnnotatedNestedAnnotationOneLevelDeep() { + //pointcut declaration, do nothing + } + + @Pointcut("within(@(@com.exadel.easyabac.model.annotation.Access *)* )") + public void methodWithinClassWithNestedAnnotationOneLevelDeep() { + //pointcut declaration, do nothing + } + + @Pointcut("execution(public * *(..)) && !execution(static * *(..))") + public void instanceMethod() { + //pointcut declaration, do nothing + } + + @Before("instanceMethod() && (methodAnnotatedNestedAnnotationOneLevelDeep() || methodWithinClassWithNestedAnnotationOneLevelDeep())") + @SuppressWarnings("unchecked") + public void methodAnnotatedWithNestedAnnotationOneLevelDeep(JoinPoint point) { + List contexts; + try { + contexts = contextBuilder.buildContexts(point); + } catch (Exception e) { + throw new AbacAnnotationParseException(e); + } + contexts.forEach(context -> context.getValidator().validate(context)); + } +} diff --git a/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AspectUtils.java b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AspectUtils.java new file mode 100644 index 0000000..8ad1ae7 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/AspectUtils.java @@ -0,0 +1,61 @@ +package com.exadel.easyabac.aspect; + +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.exception.AbacSystemException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; + +/** + * Aspect utility methods. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +class AspectUtils { + + private AspectUtils() { + //prevent instantiation + } + + /** + * Gets method argument by parameters on which the {@code annotation} is present. + * + * @param arguments the arguments + * @param parametersAnnotations the parameters annotations + * @param annotation the annotation + * @return the method argument + */ + static Object getMethodArgument(Object[] arguments, Annotation[][] parametersAnnotations, Annotation annotation) { + Access access = annotation.annotationType().getAnnotation(Access.class); + Class idAnnotationType = access.identifier(); + + for (int i = 0; i < parametersAnnotations.length; ++i) { + for (int j = 0; j < parametersAnnotations[i].length; ++j) { + Annotation parameter = parametersAnnotations[i][j]; + if (idAnnotationType.isAssignableFrom(parameter.getClass())) { + return arguments[i]; + } + } + } + return null; + } + + /** + * Gets annotation parameter value by provided method name. + * + * @param the type parameter + * @param annotation the annotation + * @param method the method + * @return the annotation parameter + * @throws AbacSystemException in case when error during getting annotation parameter + */ + @SuppressWarnings("unchecked") + static T getAnnotationParameter(Annotation annotation, String method) { + try { + return (T) annotation.getClass().getMethod(method).invoke(annotation); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new AbacSystemException(e); + } + } +} diff --git a/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/DefaultExecutionContext.java b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/DefaultExecutionContext.java new file mode 100644 index 0000000..a2ee366 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/DefaultExecutionContext.java @@ -0,0 +1,78 @@ +package com.exadel.easyabac.aspect; + +import com.exadel.easyabac.model.validation.EntityAccessValidator; +import com.exadel.easyabac.model.core.Action; +import com.exadel.easyabac.model.validation.ExecutionContext; + +import org.aspectj.lang.JoinPoint; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Default execution context, the objects of {@code DefaultExecutionContext} are immutable. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +class DefaultExecutionContext implements ExecutionContext { + + private Object entityId; + private Set requiredActions; + private EntityAccessValidator validator; + private Class accessAnnotationType; + private JoinPoint joinPoint; + + @Override + public Object getEntityId() { + return entityId; + } + + void setEntityId(Object entityId) { + this.entityId = entityId; + } + + @Override + public Set getRequiredActions() { + return requiredActions; + } + + void setRequiredActions(Set requiredActions) { + this.requiredActions = requiredActions; + } + + @Override + public EntityAccessValidator getValidator() { + return validator; + } + + public void setValidator(EntityAccessValidator validator) { + this.validator = validator; + } + + @Override + public Class getAccessAnnotationType() { + return accessAnnotationType; + } + + void setAccessAnnotationType(Class accessAnnotationType) { + this.accessAnnotationType = accessAnnotationType; + } + + @Override + public JoinPoint getJoinPoint() { + return joinPoint; + } + + void setJoinPoint(JoinPoint joinPoint) { + this.joinPoint = joinPoint; + } + + @Override + public Class getActionType() { + return getRequiredActions().stream() + .map(Action::getClass) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Required actions are not defined")); + } +} diff --git a/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/ExecutionContextBuilder.java b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/ExecutionContextBuilder.java new file mode 100644 index 0000000..b53f9cd --- /dev/null +++ b/easy-abac/abac-aspect/src/main/java/com/exadel/easyabac/aspect/ExecutionContextBuilder.java @@ -0,0 +1,73 @@ +package com.exadel.easyabac.aspect; + +import com.exadel.easyabac.model.annotation.Access; +import com.exadel.easyabac.model.validation.EntityAccessValidator; +import com.exadel.easyabac.model.core.Action; +import com.exadel.easyabac.model.validation.ExecutionContext; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Execution context builder. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +@Component +class ExecutionContextBuilder { + + @Autowired + private ApplicationContext applicationContext; + + List buildContexts(JoinPoint point) { + return getContexts(point); + } + + private List getContexts(JoinPoint point) { + MethodSignature signature = (MethodSignature) point.getSignature(); + + Class clazz = point.getTarget().getClass(); + Method method = signature.getMethod(); + + Annotation[] methodAnnotations = method.getAnnotations(); + Annotation[] classAnnotations = clazz.getAnnotations(); + + Annotation[] annotations = Stream.of(classAnnotations, methodAnnotations) + .flatMap(Arrays::stream) + .filter(annotation -> annotation.annotationType().getAnnotation(Access.class) != null) + .toArray(Annotation[]::new); + + Object[] arguments = point.getArgs(); + Annotation[][] parametersAnnotations = method.getParameterAnnotations(); + + return Stream.of(annotations) + .map(annotation -> getContext(annotation, arguments, parametersAnnotations, point)) + .collect(Collectors.toList()); + } + + private ExecutionContext getContext(Annotation annotation, Object[] arguments, Annotation[][] parametersAnnotations, JoinPoint point) { + Object argument = AspectUtils.getMethodArgument(arguments, parametersAnnotations, annotation); + Action[] actions = AspectUtils.getAnnotationParameter(annotation, Access.ACTIONS_FIELD_NAME); + Class validatorType = AspectUtils.getAnnotationParameter(annotation, Access.VALIDATOR_FIELD_NAME); + EntityAccessValidator validator = applicationContext.getBean(validatorType); + + DefaultExecutionContext context = new DefaultExecutionContext(); + context.setEntityId(argument); + context.setRequiredActions(Stream.of(actions).collect(Collectors.toSet())); + context.setAccessAnnotationType(annotation.annotationType()); + context.setValidator(validator); + context.setJoinPoint(point); + return context; + } +} diff --git a/easy-abac/abac-aspect/src/main/resources/META-INF/spring.factories b/easy-abac/abac-aspect/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..140f194 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.exadel.easyabac.aspect.AccessAspect diff --git a/easy-abac/abac-aspect/src/main/resources/abac-config.xml b/easy-abac/abac-aspect/src/main/resources/abac-config.xml new file mode 100644 index 0000000..3569a46 --- /dev/null +++ b/easy-abac/abac-aspect/src/main/resources/abac-config.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/easy-abac/abac-aspect/src/main/resources/application.properties b/easy-abac/abac-aspect/src/main/resources/application.properties new file mode 100644 index 0000000..139597f --- /dev/null +++ b/easy-abac/abac-aspect/src/main/resources/application.properties @@ -0,0 +1,2 @@ + + diff --git a/easy-abac/abac-model/README.md b/easy-abac/abac-model/README.md new file mode 100644 index 0000000..e0b3e0d --- /dev/null +++ b/easy-abac/abac-model/README.md @@ -0,0 +1,3 @@ +### Easy-abac-model + +Encapsulates the base interfaces of the framework diff --git a/easy-abac/abac-model/pom.xml b/easy-abac/abac-model/pom.xml new file mode 100644 index 0000000..9437306 --- /dev/null +++ b/easy-abac/abac-model/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + ../.. + com.exadel.security + abac-root + 1.0-RC2 + + + abac-model + jar + + + + org.springframework + spring-context + provided + + + org.aspectj + aspectjrt + + + \ No newline at end of file diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/Access.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/Access.java new file mode 100644 index 0000000..01efc50 --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/Access.java @@ -0,0 +1,37 @@ +package com.exadel.easyabac.model.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Base annotation for custom Access annotations. All custom annotations must contain {@code @Access}. + * Implementations of this annotation determine the point at which the check will be executed + * + * @author Igor Sych + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.ANNOTATION_TYPE}) +public @interface Access { + + /** + * Actions field name in annotations + */ + String ACTIONS_FIELD_NAME = "value"; + /** + * Validator field name in annotations + */ + String VALIDATOR_FIELD_NAME = "validator"; + + /** + * The annotation that indicates identifier + * + * @return class of id-type annotation + */ + Class identifier(); +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/ProtectedResource.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/ProtectedResource.java new file mode 100644 index 0000000..8025647 --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/ProtectedResource.java @@ -0,0 +1,17 @@ +package com.exadel.easyabac.model.annotation; + +import java.lang.annotation.*; + +/** + * Marks a class to be protected. + * All methods of this class must be marked by at least one access control annotation. + * {@code @PublicResource} allows to ignore this requirement. + * @see PublicResource + * + * @author Igor Sych + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface ProtectedResource { +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/PublicResource.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/PublicResource.java new file mode 100644 index 0000000..af5d61d --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/annotation/PublicResource.java @@ -0,0 +1,14 @@ +package com.exadel.easyabac.model.annotation; + +import java.lang.annotation.*; + +/** + * Marking class or method with {@code @PublicAccess} allows to ignore the requirement of easy-abac protection. + * + * @author Igor Sych + * @since 1.0-RC1 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PublicResource { +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/core/Action.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/core/Action.java new file mode 100644 index 0000000..68934c4 --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/core/Action.java @@ -0,0 +1,26 @@ +package com.exadel.easyabac.model.core; + +/** + * Base interface for all user actions + *

+ * The example of Action implementation + *

+ * public enum Entity1Action implements Action {
+ * READ,
+ * WRITE;
+ * }
+ * 
+ * + * @author Igor Sych + * @since 1.0-RC1 + */ + +public interface Action { + + /** + * Name string. + * + * @return the string + */ + String name(); +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacAnnotationParseException.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacAnnotationParseException.java new file mode 100644 index 0000000..3abe82b --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacAnnotationParseException.java @@ -0,0 +1,19 @@ +package com.exadel.easyabac.model.exception; + +/** + * Exception during parsing abac annotations. + * + * @author Gleb Bondarchuk + * @since 25.09.2019 + */ +public class AbacAnnotationParseException extends AbacSystemException { + + /** + * Instantiates a new Abac system exception. + * + * @param cause the cause + */ + public AbacAnnotationParseException(Throwable cause) { + super(cause); + } +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacSystemException.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacSystemException.java new file mode 100644 index 0000000..615f4d3 --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/exception/AbacSystemException.java @@ -0,0 +1,19 @@ +package com.exadel.easyabac.model.exception; + +/** + * The root exception application can throw. + * + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public class AbacSystemException extends RuntimeException { + + /** + * Instantiates a new Abac system exception. + * + * @param cause the cause + */ + public AbacSystemException(Throwable cause) { + super(cause); + } +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/EntityAccessValidator.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/EntityAccessValidator.java new file mode 100644 index 0000000..06aa7dd --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/EntityAccessValidator.java @@ -0,0 +1,20 @@ +package com.exadel.easyabac.model.validation; + +import com.exadel.easyabac.model.core.Action; + +/** + * The entity access validator interface. Aimed to validate entity access rights. + * + * @param the type parameter + * @author Gleb Bondarchuk + * @since 1.0-RC1 + */ +public interface EntityAccessValidator { + + /** + * Validates actions for the {@code context}. + * + * @param context the context + */ + void validate(ExecutionContext context); +} diff --git a/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/ExecutionContext.java b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/ExecutionContext.java new file mode 100644 index 0000000..d3f9698 --- /dev/null +++ b/easy-abac/abac-model/src/main/java/com/exadel/easyabac/model/validation/ExecutionContext.java @@ -0,0 +1,62 @@ +package com.exadel.easyabac.model.validation; + +import com.exadel.easyabac.model.core.Action; + +import org.aspectj.lang.JoinPoint; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Execution context abstraction. + * + * @param the type parameter + * @author Gleb Bondarchuk + * @author Igor Sych + * @since 1.0 -RC1 + */ +public interface ExecutionContext { + + /** + * Gets entity id. + * + * @param the type parameter + * @return the entity id + */ + I getEntityId(); + + /** + * Gets required actions. + * + * @return the required actions + */ + Set getRequiredActions(); + + /** + * Gets entity access validator. + * + * @return the validator + */ + EntityAccessValidator getValidator(); + + /** + * Gets access annotation type. + * + * @return the access annotation type + */ + Class getAccessAnnotationType(); + + /** + * Gets the aspectj join point. + * + * @return the join point + */ + JoinPoint getJoinPoint(); + + /** + * Gets action type. + * + * @return the action type + */ + Class getActionType(); +} diff --git a/easy-abac/pom.xml b/easy-abac/pom.xml new file mode 100644 index 0000000..1917985 --- /dev/null +++ b/easy-abac/pom.xml @@ -0,0 +1,96 @@ + + + + 4.0.0 + + + com.exadel.security + abac-root + 1.0-RC2 + + + easy-abac + + Activity Based Security Framework + + + + + com.exadel.security + abac-model + ${version} + pom + import + + + com.exadel.security + abac-annotation-processing + ${version} + pom + import + + + com.exadel.security + abac-aspect + ${version} + pom + import + + + + + + + com.exadel.security + abac-model + + + com.exadel.security + abac-annotation-processing + + + com.exadel.security + abac-aspect + + + + + + deploy + + + + org.apache.maven.plugins + maven-jar-plugin + + + empty-javadoc-jar + package + + jar + + + javadoc + ${basedir}/javadoc + + + + empty-sources-jar + package + + jar + + + sources + ${basedir}/sources + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3273e7d --- /dev/null +++ b/pom.xml @@ -0,0 +1,342 @@ + + + + 4.0.0 + + com.exadel.security + abac-root + 1.0-RC2 + + + Exadel + Activity Based Security Framework + + 1.8 + UTF-8 + + 1.9.2 + 5.1.6.RELEASE + 1.2.3 + 1.7.25 + 1.0-rc5 + 3.9 + 4.4 + 28.0-jre + 4.12 + + 3.6.1 + 3.2.0 + 3.1.1 + 3.1.2 + 1.6 + 1.6.7 + + + + easy-abac + easy-abac/abac-annotation-processing + easy-abac/abac-model + easy-abac/abac-aspect + easy-abac-demo + + + pom + + EASY-ABAC + Activity Based Security Framework + https://github.com/exadel-inc/activity-based-security-framework + + + https://github.com/exadel-inc/activity-based-security-framework + scm:git:git:/github.com/exadel-inc/activity-based-security-framework.git + scm:git:git@github.com:exadel-inc/activity-based-security-framework.git + HEAD + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + GitHub + https://github.com/exadel-inc/activity-based-security-framework/issues + + + + + easy-abac + Exadel ABAC Framework Team + Exadel, Inc. + https://exadel.com + + developer + + Europe/Minsk + + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + + + + true + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + ${java.version} + ${java.version} + ${project.source-encoding} + + + + + true + org.apache.maven.plugins + maven-jar-plugin + ${jar-plugin.version} + + + + ${project.vendor} + ${java.version} + 2 + ${project.name} + ${project.groupId}.${project.artifactId} + ${project.version} + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + + + true + org.apache.maven.plugins + maven-source-plugin + ${source-plugin.version} + + + attach-sources + package + + jar + + + + + + + true + org.apache.maven.plugins + maven-javadoc-plugin + ${javadoc-plugin.version} + + + attach-javadoc + package + + jar + + + + + ${project.source-encoding} + + + + + true + org.apache.maven.plugins + maven-gpg-plugin + ${gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + true + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + false + true + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + + + + deploy + + false + + + + + org.apache.maven.plugins + maven-source-plugin + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + org.apache.maven.plugins + maven-gpg-plugin + + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + + + + + + + + + ${project.groupId} + abac-model + ${project.version} + + + ${project.groupId} + abac-annotation-processing + ${project.version} + + + ${project.groupId} + abac-aspect + ${project.version} + + + ${project.groupId} + easy-abac + ${project.version} + + + + org.aspectj + aspectjweaver + ${org.aspectj.version} + + + org.aspectj + aspectjrt + ${org.aspectj.version} + + + + com.google.auto.service + auto-service + ${autoservice.version} + + + + org.apache.commons + commons-lang3 + ${apache.commons-lang.version} + + + org.apache.commons + commons-collections4 + ${apache.commons-collections.version} + + + + com.google.guava + guava + ${google.guava.version} + + + org.codehaus.mojo + animal-sniffer-annotations + + + org.checkerframework + checker-qual + + + + + + junit + junit + ${junit.version} + + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-web + ${spring.version} + + + org.springframework + spring-test + ${spring.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + +