1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package eu.ehri.project.acl;
21
22 import com.google.common.base.Preconditions;
23 import com.google.common.cache.CacheBuilder;
24 import com.google.common.cache.CacheLoader;
25 import com.google.common.cache.LoadingCache;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Iterables;
28 import com.google.common.collect.Lists;
29 import com.google.common.collect.Maps;
30 import com.google.common.collect.Sets;
31 import com.tinkerpop.blueprints.Direction;
32 import com.tinkerpop.blueprints.Vertex;
33 import com.tinkerpop.frames.FramedGraph;
34 import com.tinkerpop.pipes.PipeFunction;
35 import eu.ehri.project.core.GraphManager;
36 import eu.ehri.project.core.GraphManagerFactory;
37 import eu.ehri.project.definitions.Ontology;
38 import eu.ehri.project.exceptions.IntegrityError;
39 import eu.ehri.project.exceptions.PermissionDenied;
40 import eu.ehri.project.models.ContentType;
41 import eu.ehri.project.models.EntityClass;
42 import eu.ehri.project.models.Group;
43 import eu.ehri.project.models.Permission;
44 import eu.ehri.project.models.PermissionGrant;
45 import eu.ehri.project.models.base.Accessible;
46 import eu.ehri.project.models.base.Accessor;
47 import eu.ehri.project.models.base.PermissionGrantTarget;
48 import eu.ehri.project.models.base.PermissionScope;
49
50 import java.util.Collection;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.NoSuchElementException;
55 import java.util.Optional;
56 import java.util.Set;
57
58
59
60
61 public final class AclManager {
62
63 private final FramedGraph<?> graph;
64 private final GraphManager manager;
65 private final PermissionScope scope;
66 private final Set<PermissionScope> scopes;
67
68
69
70 private final LoadingCache<PermissionType, Permission> enumPermissionMap = CacheBuilder.newBuilder()
71 .build(new CacheLoader<PermissionType, Permission>() {
72 @Override
73 public Permission load(PermissionType permissionType) throws Exception {
74 return manager.getEntity(permissionType.getName(), Permission.class);
75 }
76 });
77 private final LoadingCache<ContentTypes, ContentType> enumContentTypeMap = CacheBuilder.newBuilder()
78 .build(new CacheLoader<ContentTypes, ContentType>() {
79 @Override
80 public ContentType load(ContentTypes contentTypes) throws Exception {
81 return manager.getEntity(contentTypes.getName(), ContentType.class);
82 }
83 });
84 private final LoadingCache<Permission, PermissionType> permissionEnumMap = CacheBuilder.newBuilder()
85 .build(new CacheLoader<Permission, PermissionType>() {
86 @Override
87 public PermissionType load(Permission permission) throws Exception {
88 return PermissionType.withName(permission.getId());
89 }
90 });
91 private final LoadingCache<ContentType, ContentTypes> contentTypeEnumMap = CacheBuilder.newBuilder()
92 .build(new CacheLoader<ContentType, ContentTypes>() {
93 @Override
94 public ContentTypes load(ContentType contentType) throws Exception {
95 return ContentTypes.withName(contentType.getId());
96 }
97 });
98
99
100
101
102
103
104
105 public AclManager(FramedGraph<?> graph, PermissionScope scope) {
106 this.graph = graph;
107 this.manager = GraphManagerFactory.getInstance(graph);
108 this.scope = Optional.ofNullable(scope).orElse(SystemScope.getInstance());
109 this.scopes = getAllScopes();
110 }
111
112
113
114
115
116
117 public AclManager(FramedGraph<?> graph) {
118 this(graph, SystemScope.getInstance());
119 }
120
121
122
123
124
125
126
127 public static boolean belongsToAdmin(Accessor accessor) {
128 if (accessor.isAdmin()) {
129 return true;
130 }
131 for (Accessor parent : accessor.getParents()) {
132 if (belongsToAdmin(parent)) {
133 return true;
134 }
135 }
136 return false;
137 }
138
139
140
141
142
143
144
145 public static boolean isAnonymous(Accessor accessor) {
146 Preconditions.checkNotNull(accessor, "NULL accessor given.");
147 return accessor instanceof AnonymousAccessor
148 || accessor.getId().equals(
149 Group.ANONYMOUS_GROUP_IDENTIFIER);
150 }
151
152
153
154
155
156
157
158
159 public boolean canAccess(Accessible entity, Accessor accessor) {
160 Preconditions.checkNotNull(entity, "Entity is null");
161 Preconditions.checkNotNull(accessor, "Accessor is null");
162 return getAclFilterFunction(accessor).compute(entity.asVertex());
163 }
164
165
166
167
168
169
170
171 public void removeAccessControl(Accessible entity, Accessor accessor) {
172 entity.removeAccessor(accessor);
173 }
174
175
176
177
178
179
180
181 public void setAccessors(Accessible entity,
182 Collection<Accessor> accessors) {
183 Set<Accessor> accessorVertices = Sets.newHashSet(accessors);
184 Set<Accessor> remove = Sets.newHashSet();
185 for (Accessor accessor : entity.getAccessors()) {
186 if (!accessorVertices.contains(accessor)) {
187 remove.add(accessor);
188 }
189 }
190 for (Accessor accessor : remove) {
191 entity.removeAccessor(accessor);
192 }
193 for (Accessor accessor : accessors) {
194 entity.addAccessor(accessor);
195 }
196 }
197
198
199
200
201
202
203
204
205 public InheritedItemPermissionSet getInheritedItemPermissions(
206 Accessible entity, Accessor accessor) {
207 InheritedItemPermissionSet.Builder builder
208 = new InheritedItemPermissionSet
209 .Builder(accessor.getId(), getItemPermissions(accessor, entity));
210 for (Accessor parent : accessor.getAllParents()) {
211 builder.withInheritedPermissions(parent.getId(), getItemPermissions(parent, entity));
212 }
213 return builder.build();
214 }
215
216
217
218
219
220
221
222
223 public void setItemPermissions(Accessible item, Accessor accessor,
224 Set<PermissionType> permissionSet) throws PermissionDenied {
225 checkNoGrantOnAdminOrAnon(accessor);
226 for (PermissionType t : PermissionType.values()) {
227 if (permissionSet.contains(t)) {
228 grantPermission(item, t, accessor);
229 } else {
230 revokePermission(item, t, accessor);
231 }
232 }
233 }
234
235
236
237
238
239
240
241
242 public InheritedGlobalPermissionSet getInheritedGlobalPermissions(
243 Accessor accessor) {
244 InheritedGlobalPermissionSet.Builder builder
245 = new InheritedGlobalPermissionSet
246 .Builder(accessor.getId(), getGlobalPermissions(accessor));
247 for (Accessor parent : accessor.getParents()) {
248 builder.withInheritedPermissions(parent.getId(), getGlobalPermissions(parent));
249 }
250 return builder.build();
251 }
252
253
254
255
256
257
258
259
260 public GlobalPermissionSet getGlobalPermissions(Accessor accessor) {
261 return belongsToAdmin(accessor)
262 ? getAdminPermissions()
263 : getAccessorPermissions(accessor);
264 }
265
266
267
268
269
270
271
272 public void setPermissionMatrix(Accessor accessor, GlobalPermissionSet globals)
273 throws PermissionDenied {
274 checkNoGrantOnAdminOrAnon(accessor);
275 Map<ContentTypes, Collection<PermissionType>> globalsMap = globals.asMap();
276
277
278 for (ContentTypes ct : ContentTypes.values()) {
279 ContentType target = enumContentTypeMap.getUnchecked(ct);
280 Collection<PermissionType> pset = globalsMap.containsKey(ct)
281 ? globalsMap.get(ct)
282 : Sets.<PermissionType>newHashSet();
283 for (PermissionType perm : PermissionType.values()) {
284 if (pset.contains(perm)) {
285 grantPermission(target, perm, accessor);
286 } else {
287 revokePermission(target, perm, accessor);
288 }
289 }
290 }
291 }
292
293
294
295
296
297
298
299
300
301 public PermissionGrant grantPermission(PermissionGrantTarget target,
302 PermissionType permType, Accessor accessor) {
303 assertNoGrantOnAdminOrAnon(accessor);
304
305 Optional<PermissionGrant> maybeGrant = findPermission(target, permType, accessor);
306 if (maybeGrant.isPresent()) {
307 return maybeGrant.get();
308 } else {
309 PermissionGrant grant = createPermissionGrant();
310 accessor.addPermissionGrant(grant);
311 grant.setPermission(vertexForPermission(permType));
312 grant.addTarget(target);
313 if (!isSystemScope()) {
314 grant.setScope(scope);
315 }
316 return grant;
317 }
318 }
319
320
321
322
323
324
325
326
327 public void revokePermission(Accessible entity, PermissionType permType,
328 Accessor accessor) {
329 Optional<PermissionGrant> maybeGrant = findPermission(entity, permType, accessor);
330 if (maybeGrant.isPresent()) {
331 manager.deleteVertex(maybeGrant.get().asVertex());
332 }
333 }
334
335
336
337
338
339
340 public void revokePermissionGrant(PermissionGrant grant) {
341 manager.deleteVertex(grant.asVertex());
342 }
343
344
345
346
347
348
349
350 public PipeFunction<Vertex, Boolean> getContentTypeFilterFunction() {
351 final Set<String> typeStrings = Sets.newHashSet();
352 for (ContentTypes ct : ContentTypes.values()) {
353 typeStrings.add(ct.getName());
354 }
355 return v -> v != null && typeStrings.contains(manager.getType(v));
356 }
357
358
359
360
361
362
363
364
365 public static PipeFunction<Vertex, Boolean> getAclFilterFunction(Accessor accessor) {
366 Preconditions.checkNotNull(accessor, "Accessor is null");
367 if (belongsToAdmin(accessor)) {
368 return noopFilterFunction();
369 }
370
371 final HashSet<Vertex> all = getAllAccessors(accessor);
372 return v -> {
373 Iterable<Vertex> verts = v.getVertices(Direction.OUT,
374 Ontology.IS_ACCESSIBLE_TO);
375
376
377 if (!verts.iterator().hasNext()) {
378 return true;
379 }
380
381 if (isPromoted(v)) {
382 return true;
383 }
384
385 for (Vertex other : verts) {
386 if (all.contains(other)) {
387 return true;
388 }
389 }
390 return false;
391 };
392 }
393
394
395
396
397
398
399
400
401
402 public boolean hasPermission(ContentTypes contentType, PermissionType permissionType, Accessor accessor) {
403 return hasPermission(contentType, permissionType, accessor, scopes);
404 }
405
406
407
408
409
410
411
412
413
414 public boolean hasPermission(Accessible entity, PermissionType permissionType, Accessor accessor) {
415
416
417 Set<PermissionScope> allScopes = Sets.newHashSet(scopes);
418 for (PermissionScope scope : entity.getPermissionScopes()) {
419 allScopes.add(scope);
420 }
421
422
423
424 ContentTypes contentType = getContentType(manager.getEntityClass(entity));
425 if (hasPermission(contentType, permissionType, accessor, allScopes)) {
426 return true;
427 }
428
429
430 return hasScopedPermission(entity, permissionType, accessor, allScopes);
431 }
432
433
434
435
436
437
438
439
440
441 public AclManager withScope(PermissionScope scope) {
442 return new AclManager(graph, scope);
443 }
444
445
446
447
448
449
450 public PermissionScope getScope() {
451 return scope;
452 }
453
454
455
456
457
458
459
460
461
462
463 private boolean hasPermission(ContentTypes contentType, PermissionType permissionType, Accessor accessor,
464 Collection<PermissionScope> scopes) {
465
466 ContentType contentTypeNode = enumContentTypeMap.getUnchecked(contentType);
467
468 return belongsToAdmin(accessor)
469 || hasScopedPermission(contentTypeNode, permissionType, accessor, scopes);
470 }
471
472
473
474
475
476
477
478
479
480
481 private boolean hasScopedPermission(PermissionGrantTarget target,
482 PermissionType permissionType, Accessor accessor,
483 Collection<PermissionScope> scopes) {
484
485 for (PermissionGrant grant : accessor.getPermissionGrants()) {
486 PermissionType grantPermissionType
487 = enumForPermission(grant.getPermission());
488
489
490 if (!grantPermissionType.contains(permissionType)) {
491 continue;
492 }
493
494
495 for (PermissionGrantTarget tg : grant.getTargets()) {
496 if (!target.equals(tg)) {
497 continue;
498 }
499
500
501 if (grant.getScope() == null || scopes.contains(grant.getScope())) {
502 return true;
503 }
504 }
505 }
506
507
508 for (Accessor ancestor : accessor.getParents()) {
509 if (hasScopedPermission(target, permissionType, ancestor, scopes)) {
510 return true;
511 }
512 }
513
514
515 return false;
516 }
517
518
519
520
521 private Permission vertexForPermission(PermissionType perm) {
522 return enumPermissionMap.getUnchecked(perm);
523 }
524
525
526
527
528 private PermissionType enumForPermission(Permission perm) {
529 return permissionEnumMap.getUnchecked(perm);
530 }
531
532
533
534
535
536
537
538
539
540 private List<PermissionType> getItemPermissions(Accessor accessor,
541 Accessible entity) {
542
543 if (belongsToAdmin(accessor)) {
544 return ImmutableList.copyOf(PermissionType.values());
545 } else {
546 List<PermissionType> list = Lists.newArrayList();
547
548
549
550
551 HashSet<PermissionScope> scopes = Sets.newHashSet(entity.getPermissionScopes());
552 PermissionGrantTarget target = entity.as(PermissionGrantTarget.class);
553
554 for (PermissionGrant grant : accessor.getPermissionGrants()) {
555 if (Iterables.contains(grant.getTargets(), target)) {
556 list.add(enumForPermission(grant.getPermission()));
557 } else if (grant.getScope() != null && hasContentTypeTargets(grant)) {
558
559
560 if (scopes.contains(grant.getScope())) {
561 list.add(enumForPermission(grant.getPermission()));
562 }
563 }
564 }
565 return list;
566 }
567 }
568
569
570
571
572
573 private Optional<PermissionGrant> findPermission(PermissionGrantTarget entity,
574 PermissionType permType, Accessor accessor) {
575
576 PermissionGrantTarget target = entity.as(PermissionGrantTarget.class);
577 Permission perm = enumPermissionMap.getUnchecked(permType);
578 for (PermissionGrant grant : accessor.getPermissionGrants()) {
579 if (isInScope(grant)
580 && Iterables.contains(grant.getTargets(), target)
581 && grant.getPermission().equals(perm)) {
582 return Optional.of(grant);
583 }
584 }
585 return Optional.empty();
586 }
587
588 private PermissionGrant createPermissionGrant() {
589 try {
590 Vertex vertex = manager.createVertex(
591 EntityClass.PERMISSION_GRANT.getIdGen()
592 .generateId(Lists.<String>newArrayList(), null),
593 EntityClass.PERMISSION_GRANT,
594 Maps.newHashMap());
595 return graph.frame(vertex, PermissionGrant.class);
596 } catch (IntegrityError e) {
597 e.printStackTrace();
598 throw new RuntimeException("Something very unlikely has occured because two" +
599 " supposedly-random numbers have collided. Trying again should fix this.");
600 }
601 }
602
603 private void checkNoGrantOnAdminOrAnon(Accessor accessor)
604 throws PermissionDenied {
605
606
607 if (accessor.isAdmin() || accessor.isAnonymous()) {
608 throw new PermissionDenied(
609 "Unable to grant or revoke permissions to system accounts.");
610 }
611 }
612
613 private void assertNoGrantOnAdminOrAnon(Accessor accessor) {
614
615
616
617 if (accessor.isAdmin() || accessor.isAnonymous()) {
618 throw new RuntimeException(
619 "Unable to grant or revoke permissions to system accounts.");
620 }
621 }
622
623
624
625
626
627
628
629
630
631
632 private static HashSet<Vertex> getAllAccessors(Accessor accessor) {
633
634 HashSet<Vertex> all = Sets.newHashSet();
635 if (!isAnonymous(accessor)) {
636 Iterable<Accessor> parents = accessor.getAllParents();
637 for (Accessor a : parents) {
638 all.add(a.asVertex());
639 }
640 all.add(accessor.asVertex());
641 }
642 return all;
643 }
644
645
646
647
648
649
650
651 private GlobalPermissionSet getAccessorPermissions(Accessor accessor) {
652 GlobalPermissionSet.Builder builder = GlobalPermissionSet.newBuilder();
653 for (PermissionGrant grant : accessor.getPermissionGrants()) {
654 PermissionScope scope = grant.getScope();
655 if (scope == null || scopes.contains(scope)) {
656 for (PermissionGrantTarget target : grant.getTargets()) {
657 if (manager.getEntityClass(target).equals(EntityClass.CONTENT_TYPE)) {
658 ContentType contentType = target.as(ContentType.class);
659 Permission permission = grant.getPermission();
660 if (permission != null) {
661 builder.set(
662 contentTypeEnumMap.getUnchecked(contentType),
663 permissionEnumMap.getUnchecked(permission));
664 }
665 }
666 }
667 }
668 }
669 return builder.build();
670 }
671
672
673
674
675
676
677
678 private GlobalPermissionSet getAdminPermissions() {
679 GlobalPermissionSet.Builder builder = GlobalPermissionSet.newBuilder();
680 for (ContentTypes ct : ContentTypes.values()) {
681 builder.set(ct, PermissionType.values());
682 }
683 return builder.build();
684 }
685
686
687
688
689
690
691
692 private static PipeFunction<Vertex, Boolean> noopFilterFunction() {
693 return v -> true;
694 }
695
696
697 private HashSet<PermissionScope> getAllScopes() {
698 HashSet<PermissionScope> all = Sets.newHashSet(scope.getPermissionScopes());
699 if (!isSystemScope()) {
700 all.add(scope);
701 }
702 return all;
703 }
704
705 private boolean isSystemScope() {
706 return scope.equals(SystemScope.INSTANCE);
707 }
708
709 private boolean isInScope(PermissionGrant grant) {
710
711
712 return isSystemScope()
713 && grant.getScope() == null
714 || (grant.getScope() != null && Iterables.contains(scopes,
715 grant.getScope()));
716 }
717
718
719
720
721 private ContentTypes getContentType(EntityClass type) {
722 try {
723 return ContentTypes.withName(type.getName());
724 } catch (NoSuchElementException e) {
725 throw new RuntimeException(
726 String.format("No content type found for type: '%s'", type.getName()), e);
727 }
728 }
729
730 private boolean hasContentTypeTargets(PermissionGrant grant) {
731 for (PermissionGrantTarget tg : grant.getTargets()) {
732 if (!manager.getEntityClass(tg).equals(EntityClass.CONTENT_TYPE)) {
733 return false;
734 }
735 }
736 return true;
737 }
738
739 private static boolean isPromoted(Vertex v) {
740 int promotions = Iterables.size(v.getEdges(Direction.OUT, Ontology.PROMOTED_BY));
741 return promotions > 0
742 && promotions > Iterables.size(v.getEdges(Direction.OUT, Ontology.DEMOTED_BY));
743 }
744 }