diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/adapters/ResourceManagerAdapter.java b/sources/src/main/java/com/google/solutions/jitaccess/core/adapters/ResourceManagerAdapter.java index 082dd1a1e..a0a7a2c8e 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/adapters/ResourceManagerAdapter.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/adapters/ResourceManagerAdapter.java @@ -252,7 +252,7 @@ public List testIamPermissions( } } - public List searchProjectIds(String query) throws NotAuthenticatedException, IOException { + public Set searchProjectIds(String query) throws NotAuthenticatedException, IOException { try { var client = createClient(); @@ -287,7 +287,7 @@ public List searchProjectIds(String query) throws NotAuthenticatedExc return allProjects.stream() .map(p -> new ProjectId(p.getProjectId())) - .collect(Collectors.toList()); + .collect(Collectors.toCollection(TreeSet::new)); } catch (GoogleJsonResponseException e) { switch (e.getStatusCode()) { diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectId.java b/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectId.java index bed74bf86..c24a3fcd3 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectId.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectId.java @@ -28,7 +28,7 @@ /** * Project ID for a Google Cloud project. */ -public class ProjectId { +public class ProjectId implements Comparable { private static final String PROJECT_RESOURCE_NAME_PREFIX = "//cloudresourcemanager.googleapis.com/projects/"; public final String id; @@ -94,4 +94,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(this.id); } + + @Override + public int compareTo(ProjectId o) { + return this.id.compareTo(o.id); + } } diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectRole.java b/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectRole.java index 3cf9cfa28..999eff8c8 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectRole.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/data/ProjectRole.java @@ -28,7 +28,7 @@ /** * Represents an eligible role on a project. */ -public class ProjectRole { +public class ProjectRole implements Comparable { public final RoleBinding roleBinding; public final Status status; @@ -76,6 +76,11 @@ public int hashCode() { return Objects.hash(this.roleBinding, this.status); } + @Override + public int compareTo(ProjectRole o) { + return this.roleBinding.compareTo(o.roleBinding); + } + // ------------------------------------------------------------------------- // Inner classes. // ------------------------------------------------------------------------- diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/data/RoleBinding.java b/sources/src/main/java/com/google/solutions/jitaccess/core/data/RoleBinding.java index e6c05369c..81589a9fd 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/data/RoleBinding.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/data/RoleBinding.java @@ -23,12 +23,13 @@ import com.google.common.base.Preconditions; +import java.util.Comparator; import java.util.Objects; /** * Represents a role that has been granted on a resource. */ -public class RoleBinding { +public class RoleBinding implements Comparable { public final String fullResourceName; public final String role; @@ -71,4 +72,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(this.fullResourceName, this.role); } + + @Override + public int compareTo(RoleBinding o) { + return Comparator.comparing((RoleBinding r) -> r.fullResourceName) + .thenComparing(r -> r.role) + .compare(this, o); + } } diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/data/UserId.java b/sources/src/main/java/com/google/solutions/jitaccess/core/data/UserId.java index 54a25dad3..fbc8ceb34 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/data/UserId.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/data/UserId.java @@ -25,7 +25,7 @@ import java.util.Objects; -public class UserId { +public class UserId implements Comparable { public final transient String id; public final String email; @@ -67,4 +67,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(email); } + + @Override + public int compareTo(UserId o) { + return this.email.compareTo(o.email); + } } diff --git a/sources/src/main/java/com/google/solutions/jitaccess/core/services/RoleDiscoveryService.java b/sources/src/main/java/com/google/solutions/jitaccess/core/services/RoleDiscoveryService.java index d96870616..cc0141e14 100644 --- a/sources/src/main/java/com/google/solutions/jitaccess/core/services/RoleDiscoveryService.java +++ b/sources/src/main/java/com/google/solutions/jitaccess/core/services/RoleDiscoveryService.java @@ -150,11 +150,11 @@ public Set listAvailableProjects( return roleBindings .stream() .map(b -> ProjectId.fromFullResourceName(b.fullResourceName)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); } else { // Used as alternative option if availableProjectsQuery is set and the main approach with Asset API is not working fast enough. - return new HashSet<>(resourceManagerAdapter.searchProjectIds(this.options.availableProjectsQuery)); + return resourceManagerAdapter.searchProjectIds(this.options.availableProjectsQuery); } } @@ -221,7 +221,7 @@ public Result listEligibleProjectRoles( evalResult -> "TRUE".equalsIgnoreCase(evalResult)) .stream() .map(binding -> new ProjectRole(binding, ProjectRole.Status.ACTIVATED)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); } else { activatedRoles = Set.of(); @@ -240,7 +240,7 @@ public Result listEligibleProjectRoles( evalResult -> "CONDITIONAL".equalsIgnoreCase(evalResult)) .stream() .map(binding -> new ProjectRole(binding, ProjectRole.Status.ELIGIBLE_FOR_JIT)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); } else { jitEligibleRoles = Set.of(); @@ -259,7 +259,7 @@ public Result listEligibleProjectRoles( evalResult -> "CONDITIONAL".equalsIgnoreCase(evalResult)) .stream() .map(binding -> new ProjectRole(binding, ProjectRole.Status.ELIGIBLE_FOR_MPA)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); } else { mpaEligibleRoles = Set.of(); @@ -291,9 +291,7 @@ public Result listEligibleProjectRoles( consolidatedRoles.addAll(activatedRoles); return new Result<>( - consolidatedRoles.stream() - .sorted(Comparator.comparing(r -> r.roleBinding.fullResourceName)) - .collect(Collectors.toList()), + consolidatedRoles, Stream.ofNullable(analysisResult.getNonCriticalErrors()) .flatMap(Collection::stream) .map(e -> e.getCause()) @@ -353,7 +351,7 @@ public Set listEligibleUsersForProjectRole( // Remove the caller. .filter(user -> !user.equals(callerUserId)) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(TreeSet::new)); } // ------------------------------------------------------------------------- diff --git a/sources/src/main/resources/META-INF/resources/styles.css b/sources/src/main/resources/META-INF/resources/styles.css index 7db6ab323..100d5bbef 100644 --- a/sources/src/main/resources/META-INF/resources/styles.css +++ b/sources/src/main/resources/META-INF/resources/styles.css @@ -24,6 +24,10 @@ a { height: auto; } +.mdl-stepper { + max-width: 1024px; +} + .mdl-step__transient { height: calc(100% - 32px); } diff --git a/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectId.java b/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectId.java index 1fc5adea1..fa1d36712 100644 --- a/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectId.java +++ b/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectId.java @@ -23,6 +23,10 @@ import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.TreeSet; +import java.util.stream.Collectors; + import static org.junit.jupiter.api.Assertions.*; public class TestProjectId { @@ -104,4 +108,14 @@ public void whenObjectIsDifferentType_ThenEqualsReturnsFalse() { assertFalse(id1.equals("")); } + + @Test + public void whenInTreeSet_ThenReturnsInExpectedOrder() { + var projects = List.of(new ProjectId("project-3"),new ProjectId("project-1"), new ProjectId("project-2")); + var sortedProjects = new TreeSet<>(projects); + var sortedIter = sortedProjects.iterator(); + assertEquals(sortedIter.next(), new ProjectId("project-1")); + assertEquals(sortedIter.next(), new ProjectId("project-2")); + assertEquals(sortedIter.next(), new ProjectId("project-3")); + } } diff --git a/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectRole.java b/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectRole.java index 22c3dc8fa..2c585eb61 100644 --- a/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectRole.java +++ b/sources/src/test/java/com/google/solutions/jitaccess/core/data/TestProjectRole.java @@ -23,6 +23,9 @@ import org.junit.jupiter.api.Test; +import java.util.List; +import java.util.TreeSet; + import static org.junit.jupiter.api.Assertions.*; public class TestProjectRole { @@ -133,4 +136,26 @@ public void equalsNullIsFalse() { assertFalse(role.equals(null)); } + + @Test + public void whenInTreeSet_ThenReturnsInExpectedOrder() { + var role1 = new ProjectRole( + new RoleBinding("//cloudresourcemanager.googleapis.com/projects/project-1", "role/sample1"), + ProjectRole.Status.ELIGIBLE_FOR_JIT + ); + var role2 = new ProjectRole( + new RoleBinding("//cloudresourcemanager.googleapis.com/projects/project-1", "role/sample2"), + ProjectRole.Status.ELIGIBLE_FOR_JIT + ); + var role3 = new ProjectRole( + new RoleBinding("//cloudresourcemanager.googleapis.com/projects/project-2", "role/sample1"), + ProjectRole.Status.ELIGIBLE_FOR_JIT + ); + var roles = List.of(role3,role1,role2); + var sorted = new TreeSet<>(roles); + var sortedIter = sorted.iterator(); + assertEquals(sortedIter.next(), role1); + assertEquals(sortedIter.next(), role2); + assertEquals(sortedIter.next(), role3); + } } \ No newline at end of file diff --git a/sources/src/test/java/com/google/solutions/jitaccess/core/services/TestRoleDiscoveryService.java b/sources/src/test/java/com/google/solutions/jitaccess/core/services/TestRoleDiscoveryService.java index bc3733eb9..5c803017e 100644 --- a/sources/src/test/java/com/google/solutions/jitaccess/core/services/TestRoleDiscoveryService.java +++ b/sources/src/test/java/com/google/solutions/jitaccess/core/services/TestRoleDiscoveryService.java @@ -1101,8 +1101,7 @@ public void whenResourceManagerEmpty_ThenListAvailableProjectsReturnsEmptyList() var assetAdapter = Mockito.mock(AssetInventoryAdapter.class); var resourceManagerAdapter = Mockito.mock(ResourceManagerAdapter.class); - when(resourceManagerAdapter.searchProjectIds(eq("parent:folder/0"))) - .thenReturn(List.of()); + when(resourceManagerAdapter.searchProjectIds(eq("parent:folder/0"))).thenReturn(Set.of()); var service = new RoleDiscoveryService( assetAdapter, @@ -1118,7 +1117,7 @@ public void whenResourceManagerEmpty_ThenListAvailableProjectsReturnsEmptyList() public void whenResourceManagerReturnsList_ThenListAvailableProjectsReturnsTheSameList() throws Exception { var assetAdapter = Mockito.mock(AssetInventoryAdapter.class); var resourceManagerAdapter = Mockito.mock(ResourceManagerAdapter.class); - var expectedProjectIds = List.of(new ProjectId("project-1"), new ProjectId("project-2")); + var expectedProjectIds = Set.of(new ProjectId("project-1"), new ProjectId("project-2")); when(resourceManagerAdapter.searchProjectIds(eq("parent:folder/0"))) .thenReturn(expectedProjectIds);