blob: 09f48b5f030a66a1776f500fcd3c37bffac26116 [file] [log] [blame]
Michael Hanle25dea22015-09-24 19:37:56 +02001package de.ids_mannheim.korap.security.ac;
2
3import de.ids_mannheim.korap.exceptions.EmptyResultException;
4import de.ids_mannheim.korap.exceptions.KustvaktException;
5import de.ids_mannheim.korap.exceptions.NotAuthorizedException;
6import de.ids_mannheim.korap.exceptions.StatusCodes;
7import de.ids_mannheim.korap.interfaces.EncryptionIface;
Michael Hanlf21773f2015-10-16 23:02:31 +02008import de.ids_mannheim.korap.interfaces.db.PolicyHandlerIface;
9import de.ids_mannheim.korap.interfaces.db.ResourceOperationIface;
Michael Hanle25dea22015-09-24 19:37:56 +020010import de.ids_mannheim.korap.resources.KustvaktResource;
11import de.ids_mannheim.korap.resources.Permissions;
12import de.ids_mannheim.korap.security.Parameter;
13import de.ids_mannheim.korap.security.PermissionsBuffer;
14import de.ids_mannheim.korap.security.PolicyCondition;
15import de.ids_mannheim.korap.security.SecurityPolicy;
16import de.ids_mannheim.korap.user.User;
17import de.ids_mannheim.korap.utils.KustvaktLogger;
18import org.slf4j.Logger;
19import org.slf4j.LoggerFactory;
20
21import java.util.*;
22
23/**
24 * should only be used if a resource is uniquely identifiable by either three methods: id, name or path!
25 * In any other case, use categorypolicies to retrieve policies of a certain type
26 *
27 * @author hanl
28 * @date 15/01/2014
29 */
30
31// todo: add auditing mechanism to this!
32@SuppressWarnings("all")
33public class SecurityManager<T extends KustvaktResource> {
34
35 private static final Logger secLogger = LoggerFactory
36 .getLogger(KustvaktLogger.SECURITY_LOG);
37 private static final Logger errorLogger = LoggerFactory
38 .getLogger(KustvaktLogger.ERROR_LOG);
39 private static PolicyHandlerIface policydao;
40 private static Map<Class<? extends KustvaktResource>, ResourceOperationIface> handlers;
41 private static EncryptionIface crypto;
42
43 private List<SecurityPolicy>[] policies;
44 private User user;
45
Michael Hanlf0785322015-11-13 16:14:45 +010046 private boolean silent;
Michael Hanle25dea22015-09-24 19:37:56 +020047 private PolicyEvaluator evaluator;
48 private T resource;
49
Michael Hanl19390652016-01-16 11:01:24 +010050 //todo: use simple user id if possible! --> or if not check that user has valid integer id (or use username as fallback instead)
Michael Hanle25dea22015-09-24 19:37:56 +020051 private SecurityManager(User user) {
52 this.policies = new List[1];
53 this.policies[0] = new ArrayList<>();
Michael Hanlf0785322015-11-13 16:14:45 +010054 this.silent = true;
Michael Hanle25dea22015-09-24 19:37:56 +020055 this.user = user;
Michael Hanlf0785322015-11-13 16:14:45 +010056 checkProviders();
57 }
58
59 private static void checkProviders() {
60 if (policydao == null && crypto == null && handlers == null)
61 throw new RuntimeException("providers not set!");
Michael Hanle25dea22015-09-24 19:37:56 +020062 }
63
64 public static final void setProviders(PolicyHandlerIface policyHandler,
Michael Hanl19390652016-01-16 11:01:24 +010065 EncryptionIface crypto, Collection<ResourceOperationIface> ifaces) {
Michael Hanle25dea22015-09-24 19:37:56 +020066 SecurityManager.policydao = policyHandler;
67 SecurityManager.crypto = crypto;
68 SecurityManager.handlers = new HashMap<>();
69 secLogger.info("Registering handlers: {}", Arrays.asList(ifaces));
70 for (ResourceOperationIface iface : ifaces)
71 handlers.put(iface.getType(), iface);
72 }
73
74 public static Map<Class<? extends KustvaktResource>, ResourceOperationIface> getHandlers() {
75 return handlers;
76 }
77
78 /**
79 * only allowed if the resource is uniquely identifiable by the name, if not, use path or id!
80 * Shortcut so resource values do not need to be retrieved afterwards!
81 *
82 * @param name
83 * @param user
84 * @param type
85 * @return
86 * @throws EmptyResultException
87 * @throws KustvaktException
88 */
Michael Hanlf0785322015-11-13 16:14:45 +010089 //todo: implement a fall back that throws an exception when the user NULL, but the resource has restrictions!
Michael Hanle25dea22015-09-24 19:37:56 +020090 public static SecurityManager findbyId(String id, User user, Class type,
Michael Hanlf0785322015-11-13 16:14:45 +010091 Permissions.PERMISSIONS... perms) throws KustvaktException {
Michael Hanle25dea22015-09-24 19:37:56 +020092 SecurityManager p = new SecurityManager(user);
93 p.findPolicies(id, false, perms);
94 p.resource = p.findResource(type);
95 return p;
96 }
97
98 public static SecurityManager findbyId(String id, User user,
Michael Hanlf0785322015-11-13 16:14:45 +010099 Permissions.PERMISSIONS... perms) throws KustvaktException {
Michael Hanle25dea22015-09-24 19:37:56 +0200100 SecurityManager p = new SecurityManager(user);
101 p.findPolicies(id, false, perms);
102 p.resource = p.findResource(null);
103 return p;
104 }
105
106 public static SecurityManager findbyId(Integer id, User user,
Michael Hanlf0785322015-11-13 16:14:45 +0100107 Permissions.PERMISSIONS... perms) throws KustvaktException {
Michael Hanle25dea22015-09-24 19:37:56 +0200108 SecurityManager p = new SecurityManager(user);
109 p.findPolicies(id, false, perms);
110 p.resource = p.findResource(null);
111 return p;
112 }
113
114 public static SecurityManager findbyPath(String path, User user,
115 Permissions.PERMISSIONS... perms)
116 throws NotAuthorizedException, EmptyResultException {
117 SecurityManager manager = new SecurityManager(user);
118 manager.findPolicies(path, true, perms);
119 //fixme: need a match count. if match not unique, exception. also, does parent -child relation match hold up here?
120 return manager;
121 }
122
123 public static SecurityManager init(String id, User user,
124 Permissions.PERMISSIONS... perms)
125 throws NotAuthorizedException, EmptyResultException {
126 SecurityManager p = new SecurityManager(user);
127 p.findPolicies(id, false, perms);
128 return p;
129 }
130
131 /**
132 * enables retrieval for read access only!
133 *
134 * @return
135 * @throws NotAuthorizedException
136 */
Michael Hanlf0785322015-11-13 16:14:45 +0100137 public final T getResource() throws NotAuthorizedException {
Michael Hanle25dea22015-09-24 19:37:56 +0200138 if (evaluator.isAllowed(Permissions.PERMISSIONS.READ)) {
139 return this.resource;
140 }else {
141 secLogger
142 .error("Reading the resource '{}' is not allowed for user '{}'",
143 this.resource.getPersistentID(),
144 this.user.getUsername());
145 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
146 evaluator.getResourceID());
147 }
148 }
149
150 public void updateResource(T resource)
151 throws NotAuthorizedException, KustvaktException {
152 if (evaluator.isAllowed(Permissions.PERMISSIONS.WRITE)) {
153 ResourceOperationIface iface = handlers.get(resource.getClass());
154 if (iface != null)
155 iface.updateResource(resource, this.user);
156 else
157 handlers.get(KustvaktResource.class)
158 .updateResource(resource, this.user);
159 }else {
160 secLogger
161 .error("Updating the resource '{}' is not allowed for user '{}'",
162 this.resource.getPersistentID(),
163 this.user.getUsername());
164 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
165 this.evaluator.getResourceID());
166 }
167
168 }
169
170 /**
171 * @throws NotAuthorizedException
172 * @throws KustvaktException
173 */
Michael Hanl19390652016-01-16 11:01:24 +0100174 // todo: delete only works with find, not with init constructor!
Michael Hanlf0785322015-11-13 16:14:45 +0100175 public void deleteResource()
176 throws NotAuthorizedException, KustvaktException {
Michael Hanle25dea22015-09-24 19:37:56 +0200177 if (evaluator.isAllowed(Permissions.PERMISSIONS.DELETE)) {
178 ResourceOperationIface iface = handlers
179 .get(this.resource.getClass());
180 if (iface != null)
181 iface.deleteResource(this.evaluator.getResourceID(), this.user);
182 else
183 handlers.get(KustvaktResource.class)
184 .deleteResource(this.evaluator.getResourceID(),
185 this.user);
Michael Hanl19390652016-01-16 11:01:24 +0100186 this.policydao
187 .deleteResourcePolicies(this.evaluator.getResourceID(),
188 this.user);
Michael Hanle25dea22015-09-24 19:37:56 +0200189 }else
190 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
191 this.evaluator.getResourceID());
192 }
193
Michael Hanle25dea22015-09-24 19:37:56 +0200194 // todo: type should be deprecated and return type of policies should be containers!
195 private boolean findPolicies(Object id, boolean path,
Michael Hanlf0785322015-11-13 16:14:45 +0100196 Permissions.PERMISSIONS... perms) throws EmptyResultException {
Michael Hanle25dea22015-09-24 19:37:56 +0200197 PermissionsBuffer b = new PermissionsBuffer();
198 if (perms.length == 0)
199 b.addPermission(Permissions.READ);
200 else
201 b.addPermissions(perms);
202 if (id instanceof String && !path)
203 this.policies = policydao
204 .getPolicies((String) id, this.user, b.getPbyte());
205 if (id instanceof String && path)
206 this.policies = policydao
207 .findPolicies((String) id, this.user, b.getPbyte());
208 if (id instanceof Integer)
209 this.policies = policydao
210 .getPolicies((Integer) id, this.user, b.getPbyte());
Michael Hanl19390652016-01-16 11:01:24 +0100211 // System.out.println("-------------------------------");
212 // System.out.println("LENGTH OF POLICY ARRAY " + this.policies.length);
213 // System.out.println("POLICY AT 0 " + this.policies[0]);
Michael Hanle25dea22015-09-24 19:37:56 +0200214 this.evaluator = new PolicyEvaluator(this.user, this.policies);
215
216 if (this.policies == null) {
217 KustvaktLogger.SECURITY_LOGGER
218 .error("No policies found for resource id '{}' for user '{}'",
219 id, user.getId());
220 throw new EmptyResultException(String.valueOf(id));
221 }
222 return true;
223 }
224
Michael Hanl19390652016-01-16 11:01:24 +0100225 // todo: security log shows id 'null' --> better way?
Michael Hanle25dea22015-09-24 19:37:56 +0200226 private T findResource(Class type)
227 throws NotAuthorizedException, KustvaktException {
228 if (!evaluator.isAllowed()) {
229 KustvaktLogger.SECURITY_LOGGER
230 .error("Permission denied for resource id '{}' for user '{}'",
231 this.evaluator.getResourceID(), user.getId());
232 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
233 this.evaluator.getResourceID());
234 }
235
236 ResourceOperationIface iface = handlers.get(type);
237 if (iface == null)
238 iface = handlers.get(KustvaktResource.class);
239 T resource = (T) iface
240 .findbyId(this.evaluator.getResourceID(), this.user);
Michael Hanl19390652016-01-16 11:01:24 +0100241 // todo: fix this
Michael Hanle25dea22015-09-24 19:37:56 +0200242 resource.setManaged(this.evaluator.isManaged());
243 resource.setShared(this.evaluator.isShared());
244 return resource;
245 }
246
247 private boolean checkResource(String persistentID, User user)
248 throws KustvaktException {
249 ResourceOperationIface iface = handlers.get(KustvaktResource.class);
250 return iface.findbyId(persistentID, user) != null;
251 }
252
253 public static SecurityManager register(KustvaktResource resource, User user)
254 throws KustvaktException, NotAuthorizedException {
255 SecurityManager p = new SecurityManager(user);
256 if (!user.isDemo()) {
257 if (resource.getParentID() != null) {
258 try {
259 // the owner has all rights per default, in order to be able derivate from a parent resource, he needs all permissions as well
260 // this is mostly for convenvience and database consistency, since a request query would result in not authorized, based on missing parent relation dependencies
261 // --> in order not to have a resource owner that is denied access due to missing parent relation dependency
262 SecurityManager.findbyId(resource.getParentID(), user,
Michael Hanl19390652016-01-16 11:01:24 +0100263 Permissions.PERMISSIONS.ALL);
Michael Hanle25dea22015-09-24 19:37:56 +0200264 }catch (EmptyResultException e) {
265 KustvaktLogger.SECURITY_LOGGER
266 .error("No policies found for parent '{}' for user '{}'",
267 resource.getParentID(), user.getId());
268 throw new KustvaktException(StatusCodes.EMPTY_RESULTS);
269 }
270 }
271 boolean newid = false;
272 // create persistent identifier for the resource
273 if (resource.getPersistentID() == null || resource.getPersistentID()
274 .isEmpty()) {
275 resource.setPersistentID(p.crypto.createID());
276 newid = true;
277 }
278
279 if (newid | !p.checkResource(resource.getPersistentID(), user)) {
280 resource.setOwner(user.getId());
281
282 KustvaktLogger.SECURITY_LOGGER
Michael Hanl19390652016-01-16 11:01:24 +0100283 .info("Creating Access Control structure for resource '"
Michael Hanle25dea22015-09-24 19:37:56 +0200284 + resource.getPersistentID() + "@" + resource
285 .getId() + "'");
286 // storing resource is called twice. first when this is register and later in idsbootstrap to create cstorage entry. how to unify this?
287 ResourceOperationIface iface = p.handlers
288 .get(resource.getClass());
289 if (iface != null)
290 resource.setId(iface.storeResource(resource, user));
291 else
292 // retrieve default handler for resource!
293 resource.setId(p.handlers.get(KustvaktResource.class)
294 .storeResource(resource, user));
295 }
296 p.resource = resource;
297 try {
Michael Hanl19390652016-01-16 11:01:24 +0100298 // todo: which is better? Integer id or String persistentID?
299 p.findPolicies(resource.getPersistentID(), false,
Michael Hanle25dea22015-09-24 19:37:56 +0200300 Permissions.PERMISSIONS.CREATE_POLICY,
301 Permissions.PERMISSIONS.READ_POLICY,
302 Permissions.PERMISSIONS.MODIFY_POLICY);
303 }catch (EmptyResultException e) {
304 KustvaktLogger.SECURITY_LOGGER
Michael Hanl19390652016-01-16 11:01:24 +0100305 .error("No policies found for '{}' for user '{}'. Resource could not be registered!",
Michael Hanle25dea22015-09-24 19:37:56 +0200306 resource.getPersistentID(), user.getId());
Michael Hanlf0785322015-11-13 16:14:45 +0100307 throw new KustvaktException(user.getId(),
308 StatusCodes.POLICY_CREATE_ERROR,
Michael Hanle25dea22015-09-24 19:37:56 +0200309 "Resource could not be registered",
310 resource.toString());
311 }
312 }
313 return p;
314 }
315
316 @Deprecated
317 public List<SecurityPolicy> getPoliciesList(int i) {
318 if (i < this.policies.length)
319 return this.policies[i];
320 return Collections.emptyList();
321 }
322
323 // fixme: make protected
324 public SecurityPolicy getPolicy(Integer id) {
325 for (SecurityPolicy p : this.policies[0])
326 if (p.getID() == id)
327 return p;
328 return null;
329 }
330
331 // fixme: make protected
332 public PolicyCondition getExtensional(Permissions.PERMISSIONS... pps) {
333 for (SecurityPolicy p : this.policies[0]) {
334 if (p.equalsPermission(pps)) {
335 for (PolicyCondition c : p.getConditions()) {
336 if (c.isExtensional())
337 return c;
338 }
339 }
340 }
341 return null;
342 }
343
344 private boolean matchTarget(String target) {
345 return this.resource.getPersistentID() != null && (
346 this.resource.getPersistentID() == target);
347 }
348
349 public void addPolicy(SecurityPolicy policy, Parameter... params)
350 throws KustvaktException, NotAuthorizedException {
351 if (policy.getConditions().isEmpty()) {
352 KustvaktLogger.SECURITY_LOGGER
353 .error("No conditions set for '{}' for user '{}'",
354 policy.toString(), this.user.getId());
355 throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT,
356 policy.getTarget());
357 }
358
359 if (this.policies[0] == null) {
360 KustvaktLogger.SECURITY_LOGGER
361 .error("No policies found for '{}' for user '{}'",
362 this.evaluator.getResourceID(), this.user.getId());
363 throw new NotAuthorizedException(StatusCodes.UNSUPPORTED_OPERATION,
364 policy.getTarget());
365 }
366
367 if (contains(policy)) {
368 modifyPolicy(policy);
369 return;
370 }
371
372 if (evaluator.isAllowed(Permissions.PERMISSIONS.CREATE_POLICY)) {
373 policydao.createPolicy(policy, this.user);
Michael Hanlf0785322015-11-13 16:14:45 +0100374 }else if (silent) {
Michael Hanle25dea22015-09-24 19:37:56 +0200375 KustvaktLogger.SECURITY_LOGGER
376 .error("Permission Denied (CREATE_POLICY) on '{}' for user '{}'",
377 this.evaluator.getResourceID(), this.user.getId());
378 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
379 policy.getTarget());
380 }
381
382 if (params != null && params.length > 0) {
383 for (Parameter p : params) {
384 p.setPolicy(policy);
385 policydao.createParamBinding(p);
386 }
387 }
388 this.policies[0].add(policy);
389 }
390
Michael Hanlf0785322015-11-13 16:14:45 +0100391 public void deletePolicies()
392 throws NotAuthorizedException, KustvaktException {
Michael Hanle25dea22015-09-24 19:37:56 +0200393 for (SecurityPolicy p : new ArrayList<>(this.policies[0]))
394 deletePolicy(p);
395 }
396
397 public void retainPolicies(List<SecurityPolicy> policies)
398 throws NotAuthorizedException, KustvaktException {
399 for (SecurityPolicy p : new ArrayList<>(this.policies[0])) {
400 if (!policies.contains(p))
401 this.deletePolicy(p);
402 }
403 }
404
405 public void deletePolicy(SecurityPolicy policy)
406 throws KustvaktException, NotAuthorizedException {
407 // todo: get rid of this: use sql to match policy id and target according to evaluator!
408 if (!matchTarget(policy.getTarget()))
409 // adjust message
410 throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT,
411 this.evaluator.getResourceID());
412
413 if (this.policies[0] == null) {
414 KustvaktLogger.SECURITY_LOGGER
415 .error("No policies found (DELETE_POLICY) on '{}' for '{}'",
416 this.evaluator.getResourceID(), this.user.getId());
Michael Hanlf0785322015-11-13 16:14:45 +0100417 throw new KustvaktException(user.getId(), StatusCodes.NO_POLICIES,
Michael Hanle25dea22015-09-24 19:37:56 +0200418 "no policy desicion possible",
419 this.evaluator.getResourceID());
420 }
421 if (contains(policy) && (evaluator
422 .isAllowed(Permissions.PERMISSIONS.DELETE_POLICY))) {
423 policydao.deletePolicy(policy, this.user);
Michael Hanlf0785322015-11-13 16:14:45 +0100424 }else if (silent) {
Michael Hanle25dea22015-09-24 19:37:56 +0200425 KustvaktLogger.SECURITY_LOGGER
426 .error("Permission Denied (DELETE_POLICY) on '{}' for '{}'",
427 this.evaluator.getResourceID(), this.user.getId());
428 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
429 "no policy desicion possible",
430 this.evaluator.getResourceID());
431 }
432 policydao.removeParamBinding(policy);
433
434 this.policies[0].remove(policy);
435 }
436
437 public void modifyPolicy(SecurityPolicy policy)
438 throws KustvaktException, NotAuthorizedException {
439 if (!matchTarget(policy.getTarget()))
440 throw new NotAuthorizedException(StatusCodes.ILLEGAL_ARGUMENT);
441
442 if (this.policies[0] == null) {
443 KustvaktLogger.SECURITY_LOGGER
444 .error("Operation not possible (MODIFY_POLICY) on '{}' for '{}'",
445 this.evaluator.getResourceID(), this.user.getId());
Michael Hanlf0785322015-11-13 16:14:45 +0100446 throw new KustvaktException(user.getId(), StatusCodes.NO_POLICIES,
Michael Hanle25dea22015-09-24 19:37:56 +0200447 "no policy desicion possible",
448 this.evaluator.getResourceID());
449 }
450
451 if (contains(policy) && (evaluator
452 .isAllowed(Permissions.PERMISSIONS.MODIFY_POLICY))) {
453 policydao.updatePolicy(policy, this.user);
Michael Hanlf0785322015-11-13 16:14:45 +0100454 }else if (silent) {
Michael Hanle25dea22015-09-24 19:37:56 +0200455 KustvaktLogger.SECURITY_LOGGER
456 .error("Permission Denied (DELETE_POLICY) on '{}' for '{}'",
457 this.evaluator.getResourceID(), this.user.getId());
458 throw new NotAuthorizedException(StatusCodes.PERMISSION_DENIED,
459 this.evaluator.getResourceID());
460 }
461 this.policies = policydao
462 .getPolicies((int) this.resource.getId(), this.user, null);
463 }
464
465 /**
466 * standard function for READ access on the resource
467 *
468 * @return boolean is action allowed for resource
469 */
470 public boolean isAllowed() {
471 return evaluator.isAllowed();
472 }
473
474 public boolean isAllowed(Permissions.PERMISSIONS... perm) {
475 return evaluator.isAllowed();
476 }
477
478 /**
479 * checks if that exact object already exists (compares name,
480 * conditional parameter)
481 *
482 * @param policy
483 * @return
484 */
485 public boolean contains(SecurityPolicy policy) {
486 try {
487 return policydao.checkPolicy(policy, this.user) == 1;
488 }catch (KustvaktException e) {
489 return false;
490 }
491 }
492
493}