package com.tradeshift.reaktive.actors.acl; import java.util.UUID; import io.vavr.Function1; import io.vavr.Tuple; import io.vavr.collection.HashSet; import io.vavr.collection.Map; import io.vavr.collection.Set; import io.vavr.control.Option; /** * Immutable class that manages a list of UUID entries that can have certain rights on a resource. * The UUIDs typically represent either users or user groups, but can be anything. * It can apply changes from a fixed change type C (typically another immutable data class), granting * and revoking rights. * * @param R Enumeration containing the various rights that can be granted on the resource * @param C Type of the change object that can be applied to update the ACL */ public class ACL<R,C> { /** * Creates a new, empty, ACL, based on various lambda expressions for R and C. * @param getTargetId Function that yields a target UUID for a change (or none() if that change is to be ignored) * @param getGranted Function that yields a right that was granted for a change (or none() if the change doesn't grant rights) * @param getRevoked Function that yields a right that was revoked for a change (or none() if the change doesn't revoke rights) */ public static <R,C> ACL<R,C> empty( Function1<C, Option<UUID>> getTargetId, Function1<C, Option<R>> getGranted, Function1<C, Option<R>> getRevoked ) { return ACLBuilder.of(getTargetId, getGranted, getRevoked).empty(); } private final Map<R,Set<UUID>> entries; private final ACLBuilder<R,C> builder; protected ACL(ACLBuilder<R,C> builder, Map<R, Set<UUID>> entries) { this.entries = entries; this.builder = builder; } /** * Returns a new ACL with the given change applied. */ public ACL<R,C> apply(C change) { for (UUID target: builder.getTargetId(change)) { Map<R,Set<UUID>> entries = this.entries; for (R granted: builder.getGranted(change)) { entries = entries.put(granted, entries.getOrElse(granted, HashSet.empty()).add(target)); } for (R revoked: builder.getRevoked(change)) { entries = entries.put(revoked, entries.getOrElse(revoked, HashSet.empty()).remove(target)); } return (entries.eq(this.entries)) ? this : new ACL<>(builder, entries); } return this; } /** Returns all the rights granted in this ACL */ public Map<R,Set<UUID>> getGranted() { return entries; } /** * Returns whether the given target UUID is registered as having the given right (directly). * This function doesn't take any special "admin" rights into account. */ public boolean isGranted(R right, UUID targetId) { return entries.get(right).filter(rights -> rights.contains(targetId)).isDefined(); } /** * Returns whether the given targetId is the only (i.e. last) target that is granted that right (directly). */ public boolean isOnlyGrantedTo(UUID targetId, R right) { return entries.get(right).contains(HashSet.of(targetId)); } @SuppressWarnings("rawtypes") @Override public boolean equals(Object obj) { if (!(obj instanceof ACL)) return false; return entries.eq(((ACL)obj).entries); } @Override public int hashCode() { return entries.hashCode(); } /** * Returns whether the ACL is empty, i.e. contains no entries at all. */ public boolean isEmpty() { return entries.isEmpty(); } }