public record AccessControlList()

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));
    }
  }
}