in sources/src/main/java/com/google/solutions/jitaccess/catalog/policy/AccessControlList.java [37:211]
public record AccessControlList(
@NotNull Collection<Entry> entries
){
/**
* Empty ACL, neither allows nor denies access.
*/
public static final @NotNull AccessControlList EMPTY = new AccessControlList(List.of());
/**
* Check whether access is allowed access for any of the provided
* principals.
* <p>
* Access control list entries are evaluated in order. Access is
* denied as soon as a matching deny-entry is encountered.
*/
private static boolean isAllowed(
@NotNull Iterable<PrincipalId> principals,
@NotNull Iterable<Entry> accessEntries,
int requiredAccessRights
) {
Preconditions.checkArgument(requiredAccessRights != 0, "requiredAccessMask");
//
// NB. It's possible that no single principal is granted all the requested
// access. Therefore, we can't check each principal individually, but
// instead have to calculate the effective access mask across all principals.
//
int effectiveAccessMaskForSubject = 0;
for (var principal : principals) {
for (var entry : accessEntries) {
if (entry.principal.equals(principal)) {
if (entry instanceof AllowedEntry allowedEntry) {
//
// Accumulate access granted by this entry.
//
effectiveAccessMaskForSubject |= allowedEntry.accessRights;
}
else if (entry instanceof DeniedEntry deniedEntry &&
(deniedEntry.accessRights & requiredAccessRights) != 0) {
//
// At least one of the required access bits is denied.
//
return false;
}
}
}
}
return (effectiveAccessMaskForSubject & requiredAccessRights) == requiredAccessRights;
}
/**
* @return principals that have granted the requested level of access.
*/
public Set<PrincipalId> allowedPrincipals(int requiredAccessRights) {
//
// Segment the list of entries by principal.
//
var entriesByPrincipals = this.entries
.stream()
.collect(Collectors.groupingBy(e -> e.principal));
//
// Filter down the list of principals to those that have been
// granted the requested level of access.
//
return entriesByPrincipals
.entrySet()
.stream()
.filter(e -> isAllowed(
Collections.singleton(e.getKey()), // Principal
e.getValue(), // List of access control list entries
requiredAccessRights))
.map(e -> e.getKey())
.collect(Collectors.toSet());
}
/**
* Check whether a subject is allowed access.
* <p>
* Access control list entries are evaluated in order. Access is
* denied as soon as a matching deny-entry is encountered.
*/
public boolean isAllowed(@NotNull Subject subject, int requiredAccessRights) {
Preconditions.checkArgument(requiredAccessRights != 0, "requiredAccessRights");
return isAllowed(
subject.principals()
.stream()
.filter(p -> p.isValid())
.map(p -> p.id())
.collect(Collectors.toList()),
this.entries, requiredAccessRights);
}
/**
* Check if the ACL is empty, i.e. doesn't contain any ACEs.
*/
public boolean isEmpty() {
return this.entries.isEmpty();
}
//---------------------------------------------------------------------------
// Inner classes.
//---------------------------------------------------------------------------
/**
* An access control list entry.
*/
public static abstract class Entry {
/**
* Kind of access granted, where each bit represents a
* particular kind of access.
*/
public final int accessRights;
/**
* Principal that is being granted access.
*/
public final @NotNull PrincipalId principal;
protected Entry(@NotNull PrincipalId principal, int accessRights) {
this.principal = principal;
this.accessRights = accessRights;
}
}
/**
* Entry that grants certain access.
*/
public static class AllowedEntry extends Entry {
public AllowedEntry(@NotNull PrincipalId principal, int accessMask) {
super(principal, accessMask);
}
@Override
public String toString() {
return String.format("Allow %s: %08X", this.principal, this.accessRights);
}
}
/**
* Entry that denies certain access.
*/
public static class DeniedEntry extends Entry {
public DeniedEntry(@NotNull PrincipalId principal, int accessMask) {
super(principal, accessMask);
}
@Override
public String toString() {
return String.format("Deny %s: %08X", this.principal, this.accessRights);
}
}
public static class Builder {
private final List<Entry> entries = new LinkedList<>();
public Builder allow(PrincipalId principal, int accessMask) {
this.entries.add(new AllowedEntry(principal, accessMask));
return this;
}
public Builder deny(PrincipalId principal, int accessMask) {
this.entries.add(new DeniedEntry(principal, accessMask));
return this;
}
public AccessControlList build() {
return new AccessControlList(Collections.unmodifiableList(this.entries));
}
}
}