-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP API to obtain a quick summary of adminable workspaces and visible…
… layers
- Loading branch information
Showing
33 changed files
with
2,348 additions
and
111 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
...cation/authorization-api/src/main/java/org/geoserver/acl/authorization/AccessSummary.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved | ||
* This code is licensed under the GPL 2.0 license, available at the root | ||
* application directory. | ||
*/ | ||
package org.geoserver.acl.authorization; | ||
|
||
import lombok.EqualsAndHashCode; | ||
import lombok.NonNull; | ||
|
||
import org.geoserver.acl.domain.adminrules.AdminGrantType; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.TreeMap; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
|
||
@EqualsAndHashCode | ||
public class AccessSummary { | ||
|
||
private static final WorkspaceAccessSummary HIDDEN = | ||
WorkspaceAccessSummary.builder() | ||
.workspace("*") | ||
.adminAccess(null) | ||
.addForbidden("*") | ||
.build(); | ||
|
||
/** Immutable mapping of workspace name to summary */ | ||
private Map<String, WorkspaceAccessSummary> workspaceSummaries; | ||
|
||
private AccessSummary(Map<String, WorkspaceAccessSummary> workspaceSummaries) { | ||
this.workspaceSummaries = workspaceSummaries; | ||
} | ||
|
||
public static AccessSummary of(List<WorkspaceAccessSummary> workspaces) { | ||
Map<String, WorkspaceAccessSummary> summaries = | ||
workspaces.stream() | ||
.collect( | ||
Collectors.toMap( | ||
WorkspaceAccessSummary::getWorkspace, Function.identity())); | ||
return new AccessSummary(summaries); | ||
} | ||
|
||
public List<WorkspaceAccessSummary> getWorkspaces() { | ||
return List.copyOf(workspaceSummaries.values()); | ||
} | ||
|
||
public WorkspaceAccessSummary workspace(String workspace) { | ||
return workspaceSummaries.get(workspace); | ||
} | ||
|
||
public boolean hasAdminReadAccess(@NonNull String workspaceName) { | ||
boolean user = workspaceSummaries.getOrDefault("*", HIDDEN).isUser(); | ||
return user ? user : workspaceSummaries.getOrDefault(workspaceName, HIDDEN).isUser(); | ||
} | ||
|
||
public boolean hasAdminWriteAccess(@NonNull String workspaceName) { | ||
boolean admin = workspaceSummaries.getOrDefault("*", HIDDEN).isAdmin(); | ||
return admin ? admin : workspaceSummaries.getOrDefault(workspaceName, HIDDEN).isAdmin(); | ||
} | ||
|
||
public boolean canSeeLayer(String workspaceName, @NonNull String layerName) { | ||
if (null == workspaceName) workspaceName = WorkspaceAccessSummary.NO_WORKSPACE; | ||
WorkspaceAccessSummary summary = summary(workspaceName); | ||
return summary.canSeeLayer(layerName); | ||
} | ||
|
||
private WorkspaceAccessSummary summary(@NonNull String workspaceName) { | ||
var summary = workspaceSummaries.get(workspaceName); | ||
if (null == summary) summary = workspaceSummaries.getOrDefault("*", HIDDEN); | ||
return summary; | ||
} | ||
|
||
public Set<String> visibleWorkspaces() { | ||
return workspaceSummaries.keySet(); | ||
} | ||
|
||
public Set<String> adminableWorkspaces() { | ||
return workspaceSummaries.keySet().stream() | ||
.filter(this::hasAdminWriteAccess) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
var values = new TreeMap<>(workspaceSummaries).values(); | ||
return "%s[%s]".formatted(getClass().getSimpleName(), values); | ||
} | ||
|
||
public boolean hasAdminRightsToAnyWorkspace() { | ||
return workspaceSummaries.values().stream() | ||
.map(WorkspaceAccessSummary::getAdminAccess) | ||
.anyMatch(AdminGrantType.ADMIN::equals); | ||
} | ||
} |
53 changes: 53 additions & 0 deletions
53
...authorization-api/src/main/java/org/geoserver/acl/authorization/AccessSummaryRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved | ||
* This code is licensed under the GPL 2.0 license, available at the root | ||
* application directory. | ||
*/ | ||
package org.geoserver.acl.authorization; | ||
|
||
import lombok.Builder; | ||
import lombok.NonNull; | ||
import lombok.Value; | ||
|
||
import java.util.Set; | ||
|
||
/** | ||
* Represents the converged set of visible layer names of a specific workspace for for a {@link | ||
* AccessSummaryRequest} | ||
* | ||
* @since 2.3 | ||
* @see WorkspaceAccessSummary | ||
*/ | ||
@Value | ||
@Builder(builderClassName = "Builder") | ||
public class AccessSummaryRequest { | ||
|
||
/** | ||
* The authentication user name on behalf of which the reques is performed, {@code null} only if | ||
* the request is anonymous, and hence {@link #roles} would contain some role name with | ||
* anonymous meaning (usually {@literal ROLE_ANONYMOUS}). | ||
*/ | ||
private String user; | ||
|
||
/** The authentication role names on behalf of which the request is performed. */ | ||
@NonNull private Set<String> roles; | ||
|
||
public static class Builder { | ||
// Ignore squid:S1068, private field required for the lombok-generated build() method | ||
@SuppressWarnings({"unused", "squid:S1068"}) | ||
private Set<String> roles = Set.of(); | ||
|
||
public Builder roles(String... roleNames) { | ||
if (null == roleNames) return roles(Set.of()); | ||
return roles(Set.of(roleNames)); | ||
} | ||
|
||
public Builder roles(Set<String> roleNames) { | ||
if (null == roleNames) { | ||
this.roles = Set.of(); | ||
return this; | ||
} | ||
this.roles = Set.copyOf(roleNames); | ||
return this; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
...thorization-api/src/main/java/org/geoserver/acl/authorization/WorkspaceAccessSummary.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* (c) 2024 Open Source Geospatial Foundation - all rights reserved | ||
* This code is licensed under the GPL 2.0 license, available at the root | ||
* application directory. | ||
*/ | ||
package org.geoserver.acl.authorization; | ||
|
||
import lombok.Builder; | ||
import lombok.NonNull; | ||
import lombok.Value; | ||
|
||
import org.geoserver.acl.domain.adminrules.AdminGrantType; | ||
import org.geoserver.acl.domain.rules.GrantType; | ||
|
||
import java.util.Set; | ||
import java.util.TreeSet; | ||
|
||
/** | ||
* Represents the converged set of visible layer names of a specific workspace for for a {@link | ||
* AccessSummaryRequest} | ||
* | ||
* @since 2.3 | ||
* @see AccessSummaryRequest | ||
*/ | ||
@Value | ||
@Builder(builderClassName = "Builder") | ||
public class WorkspaceAccessSummary implements Comparable<WorkspaceAccessSummary> { | ||
public static final String NO_WORKSPACE = ""; | ||
public static final String ANY = "*"; | ||
|
||
/** | ||
* The workspace name. The special value {@link #NO_WORKSPACE} represents global entities such | ||
* as global layer groups | ||
*/ | ||
@NonNull private String workspace; | ||
|
||
/** | ||
* Whether the user from the {@link AccessSummaryRequest} is an administrator for {@link | ||
* #workspace} | ||
*/ | ||
private AdminGrantType adminAccess; | ||
|
||
/** | ||
* The set of visible layer names in {@link #workspace} the user from the {@link | ||
* AccessSummaryRequest} can somehow see, even if only under specific circumstances like for a | ||
* given OWS/request combination, resulting from {@link GrantType#ALLOW allow} rules. | ||
*/ | ||
@NonNull private Set<String> allowed; | ||
|
||
/** | ||
* The set of forbidden layer names in {@link #workspace} the user from the {@link | ||
* AccessSummaryRequest} definitely cannot see, resulting from {@link GrantType#DENY deny} | ||
* rules. | ||
* | ||
* <p>Complements the {@link #allowed} list as there may be rules allowing access all layers in | ||
* a workspace after a rules denying access to specific layers in the same workspace. | ||
*/ | ||
@NonNull private Set<String> forbidden; | ||
|
||
@Override | ||
public String toString() { | ||
return "[%s: admin: %s, allowed: %s, forbidden: %s]" | ||
.formatted(workspace, adminAccess, allowed, forbidden); | ||
} | ||
|
||
public boolean canSeeLayer(String layerName) { | ||
if (allowed.contains(ANY)) { | ||
return !forbidden.contains(layerName); | ||
} | ||
return allowed.contains(layerName); | ||
} | ||
|
||
public static class Builder { | ||
|
||
private String workspace = ANY; | ||
private AdminGrantType adminAccess; | ||
private Set<String> allowed = new TreeSet<>(); | ||
private Set<String> forbidden = new TreeSet<>(); | ||
|
||
public Builder allowed(@NonNull Set<String> layers) { | ||
this.allowed = new TreeSet<>(layers); | ||
forbidden.removeAll(layers); | ||
return this; | ||
} | ||
|
||
public Builder forbidden(@NonNull Set<String> layers) { | ||
this.forbidden = new TreeSet<>(layers); | ||
allowed.removeAll(layers); | ||
return this; | ||
} | ||
|
||
public Builder addAllowed(@NonNull String layer) { | ||
allowed.add(layer); | ||
forbidden.remove(layer); | ||
return this; | ||
} | ||
|
||
public Builder addForbidden(@NonNull String layer) { | ||
forbidden.add(layer); | ||
allowed.remove(layer); | ||
return this; | ||
} | ||
|
||
public WorkspaceAccessSummary build() { | ||
Set<String> allowedLayers = conflate(allowed); | ||
Set<String> forbiddenLayers = conflate(forbidden); | ||
|
||
return new WorkspaceAccessSummary( | ||
workspace, adminAccess, allowedLayers, forbiddenLayers); | ||
} | ||
|
||
private static Set<String> conflate(Set<String> layers) { | ||
return layers.contains(ANY) ? Set.of(ANY) : Set.copyOf(layers); | ||
} | ||
} | ||
|
||
@Override | ||
public int compareTo(WorkspaceAccessSummary o) { | ||
return workspace.compareTo(o.getWorkspace()); | ||
} | ||
|
||
public boolean isAdmin() { | ||
return adminAccess == AdminGrantType.ADMIN; | ||
} | ||
|
||
public boolean isUser() { | ||
return adminAccess == AdminGrantType.USER || adminAccess == AdminGrantType.ADMIN; | ||
} | ||
} |
Oops, something went wrong.