resources = new HashMap<>();
202 |
203 | /**
204 | * Default constructor which initialize all user roles used within
205 | * application and assign permission rules to them.
206 | * @param groupRepository
207 | * @param fileRepository
208 | */
209 | @Autowired
210 | public PermissionsService(
211 | GroupRepository groupRepository,
212 | FileRepository fileRepository
213 | ) {
214 | Role user = new Role(Roles.USER);
215 | Role admin = new Role(Roles.ADMINISTRATOR, user);
216 |
217 | user.addPermissionRules(
218 | true,
219 | "group",
220 | new String[] {"view"},
221 | GroupConditions::isMember
222 | ).addPermissionRules(
223 | true,
224 | "group",
225 | new String[] {"update"},
226 | GroupConditions::isManager
227 | );
228 |
229 | admin.addPermissionRules(
230 | true,
231 | "group",
232 | "create"
233 | );
234 |
235 | roles.put(user.getName(), user);
236 | roles.put(admin.getName(), admin);
237 |
238 | // repositories which will be used to find resources by identification
239 | resources.put("group", groupRepository);
240 | resources.put("file", fileRepository);
241 | }
242 |
243 | public boolean roleExists(String role) {
244 | return roles.containsKey(role);
245 | }
246 |
247 | public Role getRole(String roleString) {
248 | Role role = roles.get(roleString);
249 | if (role == null) {
250 | throw new RuntimeException("Role '" + roleString + "' not found");
251 | }
252 |
253 | return role;
254 | }
255 |
256 | public IResourceRepository getResource(String resource) {
257 | IResourceRepository repository = resources.get(resource);
258 | if (repository == null) {
259 | throw new RuntimeException("Resource '" + resource + "' not found");
260 | }
261 |
262 | return repository;
263 | }
264 | }
265 | ```
266 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | cz.polankam.security.acl
6 | jaclp
7 | 4.2-SNAPSHOT
8 |
9 | JACLP: Java ACL Permissions library
10 | JACLP: ACL Permission library for Spring Security introduces static ACL-based role permission system with a touch of ABAC (Attribute-based access control) over resources. It is integrated within Spring Security and its expression based permission control which might be used from Authorize-like annotations over endpoints or generally methods in components.
11 | https://github.com/Neloop/jaclp
12 |
13 |
14 |
15 | MIT License
16 | https://www.opensource.org/licenses/mit-license.php
17 |
18 |
19 |
20 |
21 |
22 | Martin Polanka
23 | PolankaMartin@gmail.com
24 | polankam.cz
25 | http://www.polankam.cz
26 |
27 |
28 |
29 |
30 | UTF-8
31 | UTF-8
32 | false
33 | 17
34 | 17
35 |
36 |
37 |
38 |
39 | org.springframework.security
40 | spring-security-web
41 | 6.2.0
42 | provided
43 |
44 |
45 |
46 | org.springframework
47 | spring-tx
48 | 6.1.1
49 | provided
50 |
51 |
52 |
53 |
54 |
55 | org.junit.jupiter
56 | junit-jupiter-engine
57 | 5.10.1
58 | test
59 |
60 |
61 |
62 | org.mockito
63 | mockito-core
64 | 5.7.0
65 | test
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-surefire-plugin
75 | 3.2.2
76 |
77 |
78 | org.apache.maven.plugins
79 | maven-release-plugin
80 | 3.0.1
81 |
82 | v@{project.version}
83 | true
84 | false
85 | release
86 | deploy
87 |
88 |
89 |
90 | org.sonatype.plugins
91 | nexus-staging-maven-plugin
92 | 1.6.13
93 | true
94 |
95 | ossrh
96 | https://oss.sonatype.org/
97 | true
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | release
106 |
107 |
108 |
109 | org.apache.maven.plugins
110 | maven-source-plugin
111 | 3.3.0
112 |
113 |
114 | attach-sources
115 |
116 | jar-no-fork
117 |
118 |
119 |
120 |
121 |
122 | org.apache.maven.plugins
123 | maven-javadoc-plugin
124 | 3.6.2
125 |
126 |
127 | attach-javadocs
128 |
129 | jar
130 |
131 |
132 |
133 |
134 |
135 | org.apache.maven.plugins
136 | maven-gpg-plugin
137 | 3.1.0
138 |
139 |
140 |
141 | --pinentry-mode
142 | loopback
143 |
144 |
145 |
146 |
147 | sign-artifacts
148 | verify
149 |
150 | sign
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | scm:git:https://github.com/Neloop/jaclp.git
162 | scm:git:https://github.com/Neloop/jaclp.git
163 | https://github.com/Neloop/jaclp/tree/master
164 | HEAD
165 |
166 |
167 |
168 |
169 | ossrh
170 | https://oss.sonatype.org/content/repositories/snapshots
171 |
172 |
173 | ossrh
174 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/src/main/java/cz/polankam/security/acl/AclPermissionEvaluator.java:
--------------------------------------------------------------------------------
1 | package cz.polankam.security.acl;
2 |
3 | import cz.polankam.security.acl.exceptions.PermissionException;
4 | import cz.polankam.security.acl.exceptions.ResourceNotFoundException;
5 | import org.springframework.security.access.PermissionEvaluator;
6 | import org.springframework.security.core.Authentication;
7 | import org.springframework.security.core.GrantedAuthority;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.transaction.PlatformTransactionManager;
10 | import org.springframework.transaction.support.TransactionTemplate;
11 |
12 | import java.io.Serializable;
13 | import java.util.Collection;
14 | import java.util.List;
15 | import java.util.Objects;
16 | import java.util.Optional;
17 | import java.util.stream.Collectors;
18 |
19 | /**
20 | * Custom permission evaluator used for 'hasPermission' expressions within
21 | * authorize annotations. The implementation is based on the user roles and
22 | * permission rules created by the {@link IPermissionsService} which has to be
23 | * given at construction.
24 | *
25 | * Created by Martin Polanka
26 | */
27 | public class AclPermissionEvaluator implements PermissionEvaluator {
28 |
29 | /**
30 | * Wildcard which can be used when specifying resource or action
31 | */
32 | public static final String WILDCARD = "*";
33 |
34 |
35 | /**
36 | * Permission service which contains definition of roles and resource
37 | * repositories used for evaluation.
38 | */
39 | private final IPermissionsService permissionsService;
40 | /**
41 | * Transaction template for this class.
42 | */
43 | private final TransactionTemplate transactionTemplate;
44 |
45 | /**
46 | * Constructor.
47 | *
48 | * @param permissionsService roles definition service
49 | * @param transactionManager transaction manager, if null transactions will not be used
50 | */
51 | public AclPermissionEvaluator(IPermissionsService permissionsService,
52 | PlatformTransactionManager transactionManager) {
53 | this.permissionsService = permissionsService;
54 | // create transaction template for this class
55 | if (transactionManager != null) {
56 | this.transactionTemplate = new TransactionTemplate(transactionManager);
57 | this.transactionTemplate.setReadOnly(true);
58 | } else {
59 | this.transactionTemplate = null;
60 | }
61 | }
62 |
63 |
64 | /**
65 | * Determine if the given user with defined roles can perform action on the
66 | * resource.
67 | *
68 | * @param authentication authentication containing currently logged user
69 | * @param targetDomainObject textual representation of the resource
70 | * @param permission textual representation of the action on the resource
71 | * @return true if user can perform the action on the given resource
72 | */
73 | @Override
74 | public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
75 | if (transactionTemplate == null) {
76 | return hasPermissionInternal(authentication, targetDomainObject, permission);
77 | } else {
78 | Boolean result = transactionTemplate.execute(status ->
79 | hasPermissionInternal(authentication, targetDomainObject, permission));
80 | return result != null && result;
81 | }
82 | }
83 |
84 |
85 | /**
86 | * Determine if the given user with defined roles can perform action on the
87 | * resource with given identification.
88 | *
89 | * @param authentication authentication containing currently logged user
90 | * @param targetId identification of the resource which should be acquired
91 | * @param targetType textual representation of the resource
92 | * @param permission textual representation of the action on the resource
93 | * @return true if user can perform the action on the given resource
94 | */
95 | @Override
96 | public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
97 | if (transactionTemplate == null) {
98 | return hasPermissionInternal(authentication, targetId, targetType, permission);
99 | } else {
100 | Boolean result = transactionTemplate.execute(status ->
101 | hasPermissionInternal(authentication, targetId, targetType, permission));
102 | return result != null && result;
103 | }
104 | }
105 |
106 | ////////////////////////////////////////////////////////////////////////////
107 |
108 | private boolean hasPermissionInternal(Authentication authentication, Object targetDomainObject, Object permission) {
109 | if (authentication == null ||
110 | !(authentication.getPrincipal() instanceof UserDetails) ||
111 | !(targetDomainObject instanceof String) ||
112 | !(permission instanceof String)) {
113 | return false;
114 | }
115 |
116 | UserDetails user = (UserDetails) authentication.getPrincipal();
117 | String targetResource = (String) targetDomainObject;
118 | String permissionString = (String) permission;
119 |
120 | // check the permissions against all user roles
121 | for (GrantedAuthority authority : user.getAuthorities()) {
122 | Role role = permissionsService.getRole(authority.getAuthority());
123 | if (role == null) {
124 | // not defined role in permission service, strange, but let us continue...
125 | continue;
126 | }
127 |
128 | List rules = findMatching(role.getPermissionRules(), targetResource, permissionString);
129 | Optional firstRule = rules.stream().findFirst();
130 | if (firstRule.isPresent()) {
131 | if (firstRule.get().getCondition() != null) {
132 | throw new PermissionException("ABAC permission rule for resource '" + targetResource +
133 | "' and action '" + permissionString + "' was used in non-ABAC context");
134 | }
135 |
136 | // at least one matching rule was found, allow it or not
137 | return firstRule.get().isAllowed();
138 | }
139 | }
140 | return false;
141 | }
142 |
143 | private boolean hasPermissionInternal(Authentication authentication, Serializable targetId, String targetType, Object permission) {
144 | if (authentication == null ||
145 | !(authentication.getPrincipal() instanceof UserDetails) ||
146 | !(permission instanceof String)) {
147 | return false;
148 | }
149 |
150 | UserDetails user = (UserDetails) authentication.getPrincipal();
151 | String permissionString = (String) permission;
152 |
153 | // check the permissions against all user roles
154 | for (GrantedAuthority authority : user.getAuthorities()) {
155 | Role role = permissionsService.getRole(authority.getAuthority());
156 | if (role == null) {
157 | // not defined role in permission service, strange, but let us continue...
158 | continue;
159 | }
160 |
161 | List rules = findMatching(role.getPermissionRules(), targetType, permissionString);
162 | Optional firstRule = rules.stream().findFirst();
163 | if (firstRule.isPresent()) {
164 | // at least one matching rule was found
165 | PermissionRule rule = firstRule.get();
166 | if (rule.getCondition() != null) {
167 | // we have to find resource repository, because we were
168 | // given resource identification, after that resource is
169 | // acquired from the repository and evaluated in specified
170 | // condition
171 | IResourceRepository repository = permissionsService.getResource(rule.getResource());
172 | Optional