aboutsummaryrefslogtreecommitdiffstats
path: root/controller-api
diff options
context:
space:
mode:
authorJon Marius Venstad <venstad@gmail.com>2019-04-04 16:35:27 +0200
committerJon Marius Venstad <venstad@gmail.com>2019-04-04 16:35:27 +0200
commitf83e2033db821695a7984577a1ee18c617fc0b55 (patch)
tree0d192897ebd99c6cbc4adf4e2cd4e9329b60b0b3 /controller-api
parent37cacaa4b1944fc4faf814d19bd01225557aa7ab (diff)
Replace RoleMembership with a bound Role, using enum ProtoRole for declarations
Diffstat (limited to 'controller-api')
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ApplicationRole.java29
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ProtoRole.java101
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java126
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystem.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenant.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenantAndApplication.java10
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembership.java73
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Roles.java88
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SecurityContext.java13
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/TenantRole.java25
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/UnboundRole.java21
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembershipTest.java74
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleTest.java54
13 files changed, 349 insertions, 282 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ApplicationRole.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ApplicationRole.java
new file mode 100644
index 00000000000..b1018fb08a6
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ApplicationRole.java
@@ -0,0 +1,29 @@
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+
+/**
+ * A {@link Role} with a {@link Context} of a {@link SystemName} a {@link TenantName} and an {@link ApplicationName}.
+ *
+ * @author jonmv
+ */
+public class ApplicationRole extends Role {
+
+ ApplicationRole(ProtoRole protoRole, SystemName system, TenantName tenant, ApplicationName application) {
+ super(protoRole, Context.limitedTo(tenant, application, system));
+ }
+
+ /** Returns the {@link TenantName} this is bound to. */
+ public TenantName tenant() { return context.tenant().get(); }
+
+ /** Returns the {@link ApplicationName} this is bound to. */
+ public ApplicationName application() { return context.application().get(); }
+
+ @Override
+ public String toString() {
+ return "role '" + proto() + "' of '" + application() + "' owned by '" + tenant() + "'";
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ProtoRole.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ProtoRole.java
new file mode 100644
index 00000000000..44d0d2356bd
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/ProtoRole.java
@@ -0,0 +1,101 @@
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import java.util.EnumSet;
+import java.util.Set;
+
+/**
+ * This declares all tenant roles known to the controller. A role contains one or more {@link Policy}s which decide
+ * what actions a member of a role can perform.
+ *
+ * Optionally, some role definition also inherit all policies from a "lower ranking" role. Read the list of roles
+ * from {@code everyone} to {@code tenantAdmin}, in order, to see what policies these roles.
+ *
+ * See {@link Role} for roles bound to a context, where policies can be evaluated.
+ *
+ * @author mpolden
+ * @author jonmv
+ */
+public enum ProtoRole {
+
+ /** Deus ex machina. */
+ hostedOperator(Policy.operator),
+
+ /** Build service which may submit new applications for continuous deployment. */
+ buildService(Policy.submission,
+ Policy.applicationRead),
+
+ /** Base role which every user is part of. */
+ everyone(Policy.classifiedRead,
+ Policy.publicRead,
+ Policy.userCreate,
+ Policy.tenantCreate),
+
+ /** Application reader which can see all information about an application, its tenant and deployments. */
+ applicationReader(everyone,
+ Policy.tenantRead,
+ Policy.applicationRead,
+ Policy.deploymentRead),
+
+ /** Application developer with access to deploy to development zones. */
+ applicationDeveloper(applicationReader,
+ Policy.developmentDeployment),
+
+ /** Application operator with access to normal, operational tasks of an application. */
+ applicationOperator(applicationDeveloper,
+ Policy.applicationOperations),
+
+ /** Application administrator with full access to an already existing application, including emergency operations. */
+ applicationAdmin(applicationOperator,
+ Policy.applicationUpdate,
+ Policy.productionDeployment,
+ Policy.submission),
+
+ /** Application administrator with the additional ability to delete an application. */
+ applicationOwner(applicationOperator,
+ Policy.applicationDelete),
+
+ /** Tenant operator with admin access to all applications under the tenant, as well as the ability to create applications. */
+ tenantOperator(applicationAdmin,
+ Policy.applicationCreate),
+
+ /** Tenant admin with full access to all tenant resources, except deleting the tenant. */
+ tenantAdmin(tenantOperator,
+ Policy.applicationDelete,
+ Policy.manager,
+ Policy.tenantUpdate),
+
+ /** Tenant admin with full access to all tenant resources. */
+ tenantOwner(tenantAdmin,
+ Policy.tenantDelete),
+
+ /** Build and continuous delivery service. */ // TODO replace with buildService, when everyone is on new pipeline.
+ tenantPipeline(everyone,
+ Policy.submission,
+ Policy.deploymentPipeline,
+ Policy.productionDeployment),
+
+ /** Tenant administrator with full access to all child resources. */
+ athenzTenantAdmin(everyone,
+ Policy.tenantRead,
+ Policy.tenantUpdate,
+ Policy.tenantDelete,
+ Policy.applicationCreate,
+ Policy.applicationUpdate,
+ Policy.applicationDelete,
+ Policy.applicationOperations,
+ Policy.developmentDeployment);
+
+ private final Set<Policy> policies;
+
+ ProtoRole(Policy... policies) {
+ this.policies = EnumSet.copyOf(Set.of(policies));
+ }
+
+ ProtoRole(ProtoRole inherited, Policy... policies) {
+ this.policies = EnumSet.copyOf(Set.of(policies));
+ this.policies.addAll(inherited.policies);
+ }
+
+ Set<Policy> policies() { return policies; }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
index e452464dcd4..c98ddd5a37c 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
@@ -2,126 +2,46 @@
package com.yahoo.vespa.hosted.controller.api.role;
import java.net.URI;
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
+import java.util.Objects;
-import java.util.EnumSet;
-import java.util.Map;
-import java.util.Set;
+import static java.util.Objects.requireNonNull;
/**
- * This declares all tenant roles known to the controller. A role contains one or more {@link Policy}s which decide
- * what actions a member of a role can perform.
+ * A role is a combination of a {@link ProtoRole} and a {@link Context}, which allows evaluation
+ * of access control for a given action on a resource. Create using {@link Roles}.
*
- * Optionally, some role definition also inherit all policies from a "lower ranking" role. Read the list of roles
- * from {@code everyone} to {@code tenantAdmin}, in order, to see what policies these roles.
- *
- * @author mpolden
* @author jonmv
*/
-public class Role implements RoleInSystem, RoleInSystemWithTenant, RoleInSystemWithTenantAndApplication {
-
- /** Deus ex machina. */
- public static final RoleInSystem hostedOperator = new Role(Policy.operator);
-
- /** Build service which may submit new applications for continuous deployment. */
- public static final RoleInSystemWithTenantAndApplication buildService = new Role(Policy.submission,
- Policy.applicationRead);
-
- /** Base role which every user is part of. */
- public static final RoleInSystem everyone = new Role(Policy.classifiedRead,
- Policy.publicRead,
- Policy.userCreate,
- Policy.tenantCreate);
-
- /** Application reader which can see all information about an application, its tenant and deployments. */
- public static final RoleInSystemWithTenantAndApplication applicationReader = new Role(everyone,
- Policy.tenantRead,
- Policy.applicationRead,
- Policy.deploymentRead);
-
- /** Application developer with access to deploy to development zones. */
- public static final RoleInSystemWithTenantAndApplication applicationDeveloper = new Role(applicationReader,
- Policy.developmentDeployment);
-
- /** Application operator with access to normal, operational tasks of an application. */
- public static final RoleInSystemWithTenantAndApplication applicationOperator = new Role(applicationDeveloper,
- Policy.applicationOperations);
-
- /** Application administrator with full access to an already existing application, including emergency operations. */
- public static final RoleInSystemWithTenantAndApplication applicationAdmin = new Role(applicationOperator,
- Policy.applicationUpdate,
- Policy.productionDeployment,
- Policy.submission);
-
- /** Application administrator with the additional ability to delete an application. */
- public static final RoleInSystemWithTenantAndApplication applicationOwner = new Role(applicationOperator,
- Policy.applicationDelete);
+public abstract class Role {
- /** Tenant operator with admin access to all applications under the tenant, as well as the ability to create applications. */
- public static final RoleInSystemWithTenant tenantOperator = new Role(applicationAdmin,
- Policy.applicationCreate);
+ private final ProtoRole protoRole;
+ final Context context;
- /** Tenant admin with full access to all tenant resources, except deleting the tenant. */
- public static final RoleInSystemWithTenant tenantAdmin = new Role(tenantOperator,
- Policy.applicationDelete,
- Policy.manager,
- Policy.tenantUpdate);
-
- /** Tenant admin with full access to all tenant resources. */
- public static final RoleInSystemWithTenant tenantOwner = new Role(tenantAdmin,
- Policy.tenantDelete);
-
- /** Build and continuous delivery service. */ // TODO replace with buildService, when everyone is on new pipeline.
- public static final RoleInSystemWithTenantAndApplication tenantPipeline = new Role(everyone,
- Policy.submission,
- Policy.deploymentPipeline,
- Policy.productionDeployment);
-
- /** Tenant administrator with full access to all child resources. */
- public static final RoleInSystemWithTenant athenzTenantAdmin = new Role(everyone,
- Policy.tenantRead,
- Policy.tenantUpdate,
- Policy.tenantDelete,
- Policy.applicationCreate,
- Policy.applicationUpdate,
- Policy.applicationDelete,
- Policy.applicationOperations,
- Policy.developmentDeployment);
-
- private final Set<Policy> policies;
-
- private Role(Policy... policies) {
- this.policies = EnumSet.copyOf(Set.of(policies));
+ Role(ProtoRole protoRole, Context context) {
+ this.protoRole = requireNonNull(protoRole);
+ this.context = requireNonNull(context);
}
- private Role(Object inherited, Policy... policies) {
- this.policies = EnumSet.copyOf(Set.of(policies));
- this.policies.addAll(((Role) inherited).policies);
- }
-
- /**
- * Returns whether this role is allowed to perform action in given role context. Action is allowed if at least one
- * policy evaluates to true.
- */
- public boolean allows(Action action, URI uri, Context context) {
- return policies.stream().anyMatch(policy -> policy.evaluate(action, uri, context));
- }
+ /** Returns the proto role of this role. */
+ public ProtoRole proto() { return protoRole; }
- @Override
- public RoleMembership limitedTo(SystemName system) {
- return new RoleMembership(Map.of(this, Set.of(Context.unlimitedIn(system))));
+ /** Returns whether this role is allowed to perform the given action on the given resource. */
+ public boolean allows(Action action, URI uri) {
+ return protoRole.policies().stream().anyMatch(policy -> policy.evaluate(action, uri, context));
}
@Override
- public RoleMembership limitedTo(TenantName tenant, SystemName system) {
- return new RoleMembership(Map.of(this, Set.of(Context.limitedTo(tenant, system))));
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Role role = (Role) o;
+ return protoRole == role.protoRole &&
+ Objects.equals(context, role.context);
}
@Override
- public RoleMembership limitedTo(ApplicationName application, TenantName tenant, SystemName system) {
- return new RoleMembership(Map.of(this, Set.of(Context.limitedTo(tenant, application, system))));
+ public int hashCode() {
+ return Objects.hash(protoRole, context);
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystem.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystem.java
deleted file mode 100644
index 144a02c4987..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystem.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.yahoo.vespa.hosted.controller.api.role;
-
-import com.yahoo.config.provision.SystemName;
-
-/** A role which requires only the context of a system. */
-public interface RoleInSystem {
- RoleMembership limitedTo(SystemName system);
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenant.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenant.java
deleted file mode 100644
index ab8aa29b7cc..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenant.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.yahoo.vespa.hosted.controller.api.role;
-
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
-
-/** A role which requires the context of a system and a tenant. */
-public interface RoleInSystemWithTenant {
- RoleMembership limitedTo(TenantName tenant, SystemName system);
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenantAndApplication.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenantAndApplication.java
deleted file mode 100644
index 31a903c998a..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleInSystemWithTenantAndApplication.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.yahoo.vespa.hosted.controller.api.role;
-
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
-
-/** A role which requires the context of a system, a tenant, and an application. */
-public interface RoleInSystemWithTenantAndApplication {
- RoleMembership limitedTo(ApplicationName application, TenantName tenant, SystemName system);
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembership.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembership.java
deleted file mode 100644
index f046b0d7203..00000000000
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembership.java
+++ /dev/null
@@ -1,73 +0,0 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.api.role;
-
-import java.net.URI;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * A list of roles and their associated contexts. This defines the role membership of a tenant, and in which contexts
- * (see {@link Context}) those roles apply.
- *
- * @author mpolden
- * @author jonmv
- */
-public class RoleMembership {
-
- private final Map<Role, Set<Context>> roles;
-
- RoleMembership(Map<Role, Set<Context>> roles) {
- this.roles = roles.entrySet().stream()
- .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey(),
- entry -> Set.copyOf(entry.getValue())));
- }
-
- public RoleMembership and(RoleMembership other) {
- return new RoleMembership(Stream.concat(this.roles.entrySet().stream(),
- other.roles.entrySet().stream())
- .collect(Collectors.toMap(Map.Entry::getKey,
- Map.Entry::getValue,
- (set1, set2) -> Stream.concat(set1.stream(), set2.stream())
- .collect(Collectors.toUnmodifiableSet()))));
- }
-
- /**
- * Returns whether any role in this allows action to take place in path
- */
- public boolean allows(Action action, URI uri) {
- return roles.entrySet().stream().anyMatch(kv -> {
- Role role = kv.getKey();
- Set<Context> contexts = kv.getValue();
- return contexts.stream().anyMatch(context -> role.allows(action, uri, context));
- });
- }
-
- /**
- * Returns the set of contexts for which the given role is valid.
- */
- public Set<Context> contextsFor(Object role) { // TODO fix.
- return roles.getOrDefault((Role) role, Collections.emptySet());
- }
-
- @Override
- public String toString() {
- return "roles " + roles;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if ( ! (o instanceof RoleMembership)) return false;
- return Objects.equals(roles, ((RoleMembership) o).roles);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(roles);
- }
-
-}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Roles.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Roles.java
new file mode 100644
index 00000000000..92374dfff74
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Roles.java
@@ -0,0 +1,88 @@
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+
+import java.util.Objects;
+
+/**
+ * Use if you need to create {@link Role}s for its system.
+ *
+ * This also defines the relationship between {@link ProtoRole}s and their required {@link Context}s.
+ *
+ * @author jonmv
+ */
+public class Roles {
+
+ private final SystemName system;
+
+ /** Create a Roles which can be used to create bound roles for the given system. */
+ public Roles(SystemName system) {
+ this.system = Objects.requireNonNull(system);
+ }
+
+ // General roles.
+ /** Returns a {@link ProtoRole#hostedOperator} for the current system. */
+ public UnboundRole hostedOperator() {
+ return new UnboundRole(ProtoRole.hostedOperator, system);
+ }
+
+ /** Returns a {@link ProtoRole#everyone} for the current system. */
+ public UnboundRole everyone() {
+ return new UnboundRole(ProtoRole.everyone, system);
+ }
+
+ // Athenz based roles.
+ /** Returns a {@link ProtoRole#athenzTenantAdmin} for the current system and given tenant. */
+ public TenantRole athenzTenantAdmin(TenantName tenant) {
+ return new TenantRole(ProtoRole.athenzTenantAdmin, system, tenant);
+ }
+
+ /** Returns a {@link ProtoRole#tenantPipeline} for the current system and given tenant and application. */
+ public ApplicationRole tenantPipeline(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.tenantPipeline, system, tenant, application);
+ }
+
+ // Other identity provider based roles.
+ /** Returns a {@link ProtoRole#tenantOwner} for the current system and given tenant. */
+ public TenantRole tenantOwner(TenantName tenant) {
+ return new TenantRole(ProtoRole.tenantOwner, system, tenant);
+ }
+
+ /** Returns a {@link ProtoRole#tenantAdmin} for the current system and given tenant. */
+ public TenantRole tenantAdmin(TenantName tenant) {
+ return new TenantRole(ProtoRole.tenantAdmin, system, tenant);
+ }
+
+ /** Returns a {@link ProtoRole#tenantOperator} for the current system and given tenant. */
+ public TenantRole tenantOperator(TenantName tenant) {
+ return new TenantRole(ProtoRole.tenantOperator, system, tenant);
+ }
+
+ /** Returns a {@link ProtoRole#applicationOwner} for the current system and given tenant and application. */
+ public ApplicationRole applicationOwner(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.applicationOwner, system, tenant, application);
+ }
+
+ /** Returns a {@link ProtoRole#applicationAdmin} for the current system and given tenant and application. */
+ public ApplicationRole applicationAdmin(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.applicationAdmin, system, tenant, application);
+ }
+
+ /** Returns a {@link ProtoRole#applicationOperator} for the current system and given tenant and application. */
+ public ApplicationRole applicationOperator(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.applicationOperator, system, tenant, application);
+ }
+
+ /** Returns a {@link ProtoRole#applicationDeveloper} for the current system and given tenant and application. */
+ public ApplicationRole applicationDeveloper(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.applicationDeveloper, system, tenant, application);
+ }
+
+ /** Returns a {@link ProtoRole#applicationReader} for the current system and given tenant and application. */
+ public ApplicationRole applicationReader(TenantName tenant, ApplicationName application) {
+ return new ApplicationRole(ProtoRole.applicationReader, system, tenant, application);
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SecurityContext.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SecurityContext.java
index 1ad98e761f5..41444258a68 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SecurityContext.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/SecurityContext.java
@@ -3,24 +3,27 @@ package com.yahoo.vespa.hosted.controller.api.role;
import java.security.Principal;
import java.util.Objects;
+import java.util.Set;
+
+import static java.util.Objects.requireNonNull;
public class SecurityContext {
public static final String ATTRIBUTE_NAME = SecurityContext.class.getName();
private final Principal principal;
- private final RoleMembership roles;
+ private final Set<Role> roles;
- public SecurityContext(Principal principal, RoleMembership roles) {
- this.principal = principal;
- this.roles = roles;
+ public SecurityContext(Principal principal, Set<Role> roles) {
+ this.principal = requireNonNull(principal);
+ this.roles = Set.copyOf(roles);
}
public Principal principal() {
return principal;
}
- public RoleMembership roles() {
+ public Set<Role> roles() {
return roles;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/TenantRole.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/TenantRole.java
new file mode 100644
index 00000000000..c3f8a5ef1b8
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/TenantRole.java
@@ -0,0 +1,25 @@
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+
+/**
+ * A {@link Role} with a {@link Context} of a {@link SystemName} and a {@link TenantName}.
+ *
+ * @author jonmv
+ */
+public class TenantRole extends Role {
+
+ TenantRole(ProtoRole protoRole, SystemName system, TenantName tenant) {
+ super(protoRole, Context.limitedTo(tenant, system));
+ }
+
+ /** Returns the {@link TenantName} this is bound to. */
+ public TenantName tenant() { return context.tenant().get(); }
+
+ @Override
+ public String toString() {
+ return "role '" + proto() + "' of '" + tenant() + "'";
+ }
+
+}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/UnboundRole.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/UnboundRole.java
new file mode 100644
index 00000000000..ba01d949713
--- /dev/null
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/UnboundRole.java
@@ -0,0 +1,21 @@
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import com.yahoo.config.provision.SystemName;
+
+/**
+ * A {@link Role} with a {@link Context} of only a {@link SystemName}.
+ *
+ * @author jonmv
+ */
+public class UnboundRole extends Role {
+
+ UnboundRole(ProtoRole protoRole, SystemName system) {
+ super(protoRole, Context.unlimitedIn(system));
+ }
+
+ @Override
+ public String toString() {
+ return "role '" + proto() + "'";
+ }
+
+}
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembershipTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembershipTest.java
deleted file mode 100644
index c2eeb17c367..00000000000
--- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleMembershipTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.controller.api.role;
-
-import com.yahoo.config.provision.ApplicationName;
-import com.yahoo.config.provision.SystemName;
-import com.yahoo.config.provision.TenantName;
-import org.junit.Test;
-
-import java.net.URI;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @author mpolden
- */
-public class RoleMembershipTest {
-
- @Test
- public void operator_membership() {
- RoleMembership roles = Role.hostedOperator.limitedTo(SystemName.main);
-
- // Operator actions
- assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
- assertTrue(roles.allows(Action.create, URI.create("/controller/v1/foo")));
- assertTrue(roles.allows(Action.update, URI.create("/os/v1/bar")));
- assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
- assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
- }
-
- @Test
- public void tenant_membership() {
- RoleMembership roles = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main);
- assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
- assertFalse("Deny access to operator API", roles.allows(Action.create, URI.create("/controller/v1/foo")));
- assertFalse("Deny access to other tenant and app", roles.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
- assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
-
- RoleMembership multiContext = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main)
- .and(Role.athenzTenantAdmin.limitedTo(TenantName.from("t2"), SystemName.main));
- assertFalse("Deny access to other tenant and app", multiContext.allows(Action.update, URI.create("/application/v4/tenant/t3/application/a3")));
- assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
- assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
-
- RoleMembership publicSystem = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.vaas);
- assertFalse(publicSystem.allows(Action.read, URI.create("/controller/v1/foo")));
- assertTrue(publicSystem.allows(Action.read, URI.create("/badge/v1/badge")));
- assertTrue(multiContext.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
- }
-
- @Test
- public void build_service_membership() {
- RoleMembership roles = Role.tenantPipeline.limitedTo(ApplicationName.from("a1"), TenantName.from("t1"), SystemName.vaas);
- assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
- assertFalse(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
- assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport")));
- assertFalse("No global read access", roles.allows(Action.read, URI.create("/controller/v1/foo")));
- }
-
- @Test
- public void multi_role_membership() {
- RoleMembership roles = Role.athenzTenantAdmin.limitedTo(TenantName.from("t1"), SystemName.main)
- .and(Role.tenantPipeline.limitedTo(ApplicationName.from("a1"), TenantName.from("t1"), SystemName.main));
- assertFalse(roles.allows(Action.create, URI.create("/not/explicitly/defined")));
- assertFalse(roles.allows(Action.create, URI.create("/controller/v1/foo")));
- assertTrue(roles.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport")));
- assertTrue(roles.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
- assertTrue("Global read access", roles.allows(Action.read, URI.create("/controller/v1/foo")));
- assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/")));
- assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/d/nodes")));
- assertTrue("Dashboard read access", roles.allows(Action.read, URI.create("/statuspage/v1/incidents")));
- }
-
-}
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleTest.java
new file mode 100644
index 00000000000..1badd157b1b
--- /dev/null
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/role/RoleTest.java
@@ -0,0 +1,54 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.api.role;
+
+import com.yahoo.config.provision.ApplicationName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.TenantName;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author mpolden
+ */
+public class RoleTest {
+
+ @Test
+ public void operator_membership() {
+ Role role = new Roles(SystemName.main).hostedOperator();
+
+ // Operator actions
+ assertFalse(role.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertTrue(role.allows(Action.create, URI.create("/controller/v1/foo")));
+ assertTrue(role.allows(Action.update, URI.create("/os/v1/bar")));
+ assertTrue(role.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ assertTrue(role.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
+ }
+
+ @Test
+ public void tenant_membership() {
+ Role role = new Roles(SystemName.main).athenzTenantAdmin(TenantName.from("t1"));
+ assertFalse(role.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertFalse("Deny access to operator API", role.allows(Action.create, URI.create("/controller/v1/foo")));
+ assertFalse("Deny access to other tenant and app", role.allows(Action.update, URI.create("/application/v4/tenant/t2/application/a2")));
+ assertTrue(role.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+
+ Role publicSystem = new Roles(SystemName.vaas).athenzTenantAdmin(TenantName.from("t1"));
+ assertFalse(publicSystem.allows(Action.read, URI.create("/controller/v1/foo")));
+ assertTrue(publicSystem.allows(Action.read, URI.create("/badge/v1/badge")));
+ assertTrue(publicSystem.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ }
+
+ @Test
+ public void build_service_membership() {
+ Role role = new Roles(SystemName.vaas).tenantPipeline(TenantName.from("t1"), ApplicationName.from("a1"));
+ assertFalse(role.allows(Action.create, URI.create("/not/explicitly/defined")));
+ assertFalse(role.allows(Action.update, URI.create("/application/v4/tenant/t1/application/a1")));
+ assertTrue(role.allows(Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport")));
+ assertFalse("No global read access", role.allows(Action.read, URI.create("/controller/v1/foo")));
+ }
+
+}