Skip to content

Commit

Permalink
b/325372861 Extract logic to build entitlement set into base class (#294
Browse files Browse the repository at this point in the history
)

* Convert ProjectRoleRepository to abstract class
* Move logic from EntitlementSet to ProjectRoleRepository
  • Loading branch information
jpassing authored Feb 27, 2024
1 parent 6965f44 commit 7790480
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 164 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,111 +50,7 @@ public record EntitlementSet<TId extends EntitlementId>(
assert expiredEntitlements.stream().allMatch(e -> e.status() == Entitlement.Status.EXPIRED);
}

public static <T extends EntitlementId> @NotNull EntitlementSet<T> build(
@NotNull Set<Entitlement<T>> availableEntitlements,
@NotNull Set<ActivatedEntitlement<T>> validActivations,
@NotNull Set<ActivatedEntitlement<T>> expiredActivations,
Set<String> warnings
)
{
assert availableEntitlements.stream().allMatch(e -> e.status() == Entitlement.Status.AVAILABLE);
assert validActivations.stream().noneMatch(id -> expiredActivations.contains(id));
assert expiredActivations.stream().noneMatch(id -> validActivations.contains(id));

//
// Return a set containing:
//
// 1. Available entitlements
// 2. Active entitlements
//
// where (1) and (2) don't overlap.
//
// Expired entitlements are ignored.
//
var availableAndInactive = availableEntitlements
.stream()
.filter(ent -> validActivations
.stream()
.noneMatch(active -> active.entitlementId().equals(ent.id())))
.collect(Collectors.toCollection(TreeSet::new));

assert availableAndInactive.stream().noneMatch(e -> validActivations.contains(e.id()));

var current = new TreeSet<Entitlement<T>>(availableAndInactive);
for (var validActivation : validActivations) {
//
// Find the corresponding entitlement to determine
// whether this is JIT or MPA-eligible.
//
var correspondingEntitlement = availableEntitlements
.stream()
.filter(ent -> ent.id().equals(validActivation.entitlementId()))
.findFirst();
if (correspondingEntitlement.isPresent()) {
current.add(new Entitlement<>(
validActivation.entitlementId(),
correspondingEntitlement.get().name(),
correspondingEntitlement.get().activationType(),
Entitlement.Status.ACTIVE,
validActivation.validity));
}
else {
//
// Active, but no longer available for activation.
//
current.add(new Entitlement<>(
validActivation.entitlementId(),
validActivation.entitlementId().id(),
ActivationType.NONE,
Entitlement.Status.ACTIVE,
validActivation.validity));
}
}

var expired = new TreeSet<Entitlement<T>>();
for (var expiredActivation : expiredActivations) {
//
// Find the corresponding entitlement to determine
// whether this is currently (*) JIT or MPA-eligible.
//
// (*) it might have changed in the meantime, but that's ok.
//
var correspondingEntitlement = availableEntitlements
.stream()
.filter(ent -> ent.id().equals(expiredActivation.entitlementId()))
.findFirst();
if (correspondingEntitlement.isPresent()) {
expired.add(new Entitlement<>(
expiredActivation.entitlementId(),
correspondingEntitlement.get().name(),
correspondingEntitlement.get().activationType(),
Entitlement.Status.EXPIRED,
expiredActivation.validity));
}
else {
//
// Active, but no longer available for activation.
//
expired.add(new Entitlement<>(
expiredActivation.entitlementId(),
expiredActivation.entitlementId().id(),
ActivationType.NONE,
Entitlement.Status.EXPIRED,
expiredActivation.validity));
}
}

return new EntitlementSet<>(current, expired, warnings);
}

public static <TId extends EntitlementId> @NotNull EntitlementSet<TId> empty() {
return new EntitlementSet<TId>(new TreeSet<>(), new TreeSet<>(), Set.of());
}

public record ActivatedEntitlement<TId>(TId entitlementId, TimeSpan validity) {
public ActivatedEntitlement {
Preconditions.checkNotNull(entitlementId, "entitlementId");
Preconditions.checkNotNull(validity, "validity");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
* are annotated with a special IAM condition (making the binding
* "eligible").
*/
public class AssetInventoryRepository implements ProjectRoleRepository {
public class AssetInventoryRepository extends ProjectRoleRepository {
public static final String GROUP_PREFIX = "group:";
public static final String USER_PREFIX = "user:";

Expand Down Expand Up @@ -210,8 +210,8 @@ public SortedSet<ProjectId> findProjectsWithEntitlements(
// Find temporary bindings that reflect activations and sort out which
// ones are still active and which ones have expired.
//
var allActive = new HashSet<EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>>();
var allExpired = new HashSet<EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>>();
var allActive = new HashSet<ActivatedEntitlement<ProjectRoleBinding>>();
var allExpired = new HashSet<ActivatedEntitlement<ProjectRoleBinding>>();

for (var binding : allBindings.stream()
// Only temporary access bindings.
Expand All @@ -228,19 +228,19 @@ public SortedSet<ProjectId> findProjectsWithEntitlements(
}

if (isValid && statusesToInclude.contains(Entitlement.Status.ACTIVE)) {
allActive.add(new EntitlementSet.ActivatedEntitlement<>(
allActive.add(new ActivatedEntitlement<>(
new ProjectRoleBinding(new RoleBinding(projectId, binding.getRole())),
condition.getValidity()));
}

if (!isValid && statusesToInclude.contains(Entitlement.Status.EXPIRED)) {
allExpired.add(new EntitlementSet.ActivatedEntitlement<>(
allExpired.add(new ActivatedEntitlement<>(
new ProjectRoleBinding(new RoleBinding(projectId, binding.getRole())),
condition.getValidity()));
}
}

return EntitlementSet.build(allAvailable, allActive, allExpired, Set.of());
return buildEntitlementSet(allAvailable, allActive, allExpired, Set.of());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
* are annotated with a special IAM condition (making the binding
* "eligible").
*/
public class PolicyAnalyzerRepository implements ProjectRoleRepository {
public class PolicyAnalyzerRepository extends ProjectRoleRepository {
private final @NotNull Options options;
private final @NotNull PolicyAnalyzerClient policyAnalyzerClient;

Expand Down Expand Up @@ -246,7 +246,7 @@ private record ConditionalRoleBinding(RoleBinding binding, Expr condition) {}
.toList());
}

var allActive = new HashSet<EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>>();
var allActive = new HashSet<ActivatedEntitlement<ProjectRoleBinding>>();
if (statusesToInclude.contains(Entitlement.Status.ACTIVE)) {
//
// Find role bindings which have already been activated.
Expand All @@ -262,13 +262,13 @@ private record ConditionalRoleBinding(RoleBinding binding, Expr condition) {}

allActive.addAll(activeBindings
.stream()
.map(conditionalBinding -> new EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>(
.map(conditionalBinding -> new ActivatedEntitlement<ProjectRoleBinding>(
new ProjectRoleBinding(conditionalBinding.binding),
new TemporaryIamCondition(conditionalBinding.condition.getExpression()).getValidity()))
.collect(Collectors.toSet()));
}

var allExpired = new HashSet<EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>>();
var allExpired = new HashSet<ActivatedEntitlement<ProjectRoleBinding>>();
if (statusesToInclude.contains(Entitlement.Status.EXPIRED)) {
//
// Do the same for conditions that evaluated to FALSE.
Expand All @@ -281,7 +281,7 @@ private record ConditionalRoleBinding(RoleBinding binding, Expr condition) {}

allExpired.addAll(activeBindings
.stream()
.map(conditionalBinding -> new EntitlementSet.ActivatedEntitlement<ProjectRoleBinding>(
.map(conditionalBinding -> new ActivatedEntitlement<ProjectRoleBinding>(
new ProjectRoleBinding(conditionalBinding.binding),
new TemporaryIamCondition(conditionalBinding.condition.getExpression()).getValidity()))
.collect(Collectors.toSet()));
Expand All @@ -292,7 +292,7 @@ private record ConditionalRoleBinding(RoleBinding binding, Expr condition) {}
.map(e -> e.getCause())
.collect(Collectors.toSet());

return EntitlementSet.build(allAvailable, allActive, allExpired, warnings);
return buildEntitlementSet(allAvailable, allActive, allExpired, warnings);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
//
// Copyright 2024 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//

package com.google.solutions.jitaccess.core.catalog.project;

import com.google.common.base.Preconditions;
import com.google.solutions.jitaccess.cel.TimeSpan;
import com.google.solutions.jitaccess.core.AccessException;
import com.google.solutions.jitaccess.core.ProjectId;
import com.google.solutions.jitaccess.core.UserEmail;
import com.google.solutions.jitaccess.core.catalog.ActivationType;
import com.google.solutions.jitaccess.core.catalog.Entitlement;
import com.google.solutions.jitaccess.core.catalog.EntitlementId;
import com.google.solutions.jitaccess.core.catalog.EntitlementSet;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.EnumSet;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
* Repository for ProjectRoleBinding-based entitlements.
*/
public interface ProjectRoleRepository {
public abstract class ProjectRoleRepository {

/**
* Find projects that a user has standing, JIT-, or MPA-eligible access to.
*/
SortedSet<ProjectId> findProjectsWithEntitlements(
abstract SortedSet<ProjectId> findProjectsWithEntitlements(
UserEmail user
) throws AccessException, IOException;

/**
* List entitlements for the given user.
*/
EntitlementSet<ProjectRoleBinding> findEntitlements(
abstract EntitlementSet<ProjectRoleBinding> findEntitlements(
UserEmail user,
ProjectId projectId,
EnumSet<ActivationType> typesToInclude,
Expand All @@ -37,8 +64,114 @@ EntitlementSet<ProjectRoleBinding> findEntitlements(
/**
* List users that hold an eligible role binding.
*/
Set<UserEmail> findEntitlementHolders(
abstract Set<UserEmail> findEntitlementHolders(
ProjectRoleBinding roleBinding,
ActivationType activationType
) throws AccessException, IOException;

/**
* Helper methods for building an EntitlementSet.
*/
static <T extends EntitlementId> @NotNull EntitlementSet<T> buildEntitlementSet(
@NotNull Set<Entitlement<T>> availableEntitlements,
@NotNull Set<ActivatedEntitlement<T>> validActivations,// TODO(later): rename to active
@NotNull Set<ActivatedEntitlement<T>> expiredActivations,
Set<String> warnings
) {
assert availableEntitlements.stream().allMatch(e -> e.status() == Entitlement.Status.AVAILABLE);
assert validActivations.stream().noneMatch(id -> expiredActivations.contains(id));
assert expiredActivations.stream().noneMatch(id -> validActivations.contains(id));

//
// Return a set containing:
//
// 1. Available entitlements
// 2. Active entitlements
//
// where (1) and (2) don't overlap.
//
// Expired entitlements are ignored.
//
var availableAndInactive = availableEntitlements
.stream()
.filter(ent -> validActivations
.stream()
.noneMatch(active -> active.entitlementId().equals(ent.id())))
.collect(Collectors.toCollection(TreeSet::new));

assert availableAndInactive.stream().noneMatch(e -> validActivations.contains(e.id()));

var current = new TreeSet<Entitlement<T>>(availableAndInactive);
for (var validActivation : validActivations) {
//
// Find the corresponding entitlement to determine
// whether this is JIT or MPA-eligible.
//
var correspondingEntitlement = availableEntitlements
.stream()
.filter(ent -> ent.id().equals(validActivation.entitlementId()))
.findFirst();
if (correspondingEntitlement.isPresent()) {
current.add(new Entitlement<>(
validActivation.entitlementId(),
correspondingEntitlement.get().name(),
correspondingEntitlement.get().activationType(),
Entitlement.Status.ACTIVE,
validActivation.validity()));
}
else {
//
// Active, but no longer available for activation.
//
current.add(new Entitlement<>(
validActivation.entitlementId(),
validActivation.entitlementId().id(),
ActivationType.NONE,
Entitlement.Status.ACTIVE,
validActivation.validity()));
}
}

var expired = new TreeSet<Entitlement<T>>();
for (var expiredActivation : expiredActivations) {
//
// Find the corresponding entitlement to determine
// whether this is currently (*) JIT or MPA-eligible.
//
// (*) it might have changed in the meantime, but that's ok.
//
var correspondingEntitlement = availableEntitlements
.stream()
.filter(ent -> ent.id().equals(expiredActivation.entitlementId()))
.findFirst();
if (correspondingEntitlement.isPresent()) {
expired.add(new Entitlement<>(
expiredActivation.entitlementId(),
correspondingEntitlement.get().name(),
correspondingEntitlement.get().activationType(),
Entitlement.Status.EXPIRED,
expiredActivation.validity()));
}
else {
//
// Active, but no longer available for activation.
//
expired.add(new Entitlement<>(
expiredActivation.entitlementId(),
expiredActivation.entitlementId().id(),
ActivationType.NONE,
Entitlement.Status.EXPIRED,
expiredActivation.validity()));
}
}

return new EntitlementSet<>(current, expired, warnings);
}

record ActivatedEntitlement<TId>(TId entitlementId, TimeSpan validity) {
public ActivatedEntitlement {
Preconditions.checkNotNull(entitlementId, "entitlementId");
Preconditions.checkNotNull(validity, "validity");
}
}
}
Loading

0 comments on commit 7790480

Please sign in to comment.