diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2019-04-15 11:58:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-15 11:58:40 +0200 |
commit | babfc4859ef4586711eb150b87c02415132a274b (patch) | |
tree | 57ab49955df4a14b35192c47ac7b797dcaf423ab | |
parent | 62ae43a918e2ab66ddaaa4f3964cfa255e5aa307 (diff) | |
parent | 132c65ea92960f583ada0db4fcf5796d85bf6a4c (diff) |
Merge pull request #9129 from vespa-engine/jvenstad/really-hide-system-in-role-API
Jvenstad/really hide system in role api
25 files changed, 323 insertions, 393 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/user/UserRoles.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/user/Roles.java index 479fcbd2589..0eff7de3f9f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/user/UserRoles.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/user/Roles.java @@ -5,75 +5,60 @@ import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.api.role.ApplicationRole; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.api.role.TenantRole; import java.util.List; -import static java.util.Objects.requireNonNull; - /** * Validation, utility and serialization methods for roles used in user management. * * @author jonmv */ -public class UserRoles { - - private final Roles roles; +public class Roles { - /** Creates a new UserRoles which can be used for serialisation and listing of bound user roles. */ - public UserRoles(Roles roles) { - this.roles = requireNonNull(roles); - } + private Roles() { } /** Returns the list of {@link TenantRole}s a {@link UserId} may be a member of. */ - public List<TenantRole> tenantRoles(TenantName tenant) { - return List.of(roles.tenantOwner(tenant), - roles.tenantAdmin(tenant), - roles.tenantOperator(tenant)); + public static List<TenantRole> tenantRoles(TenantName tenant) { + return List.of(Role.tenantOwner(tenant), + Role.tenantAdmin(tenant), + Role.tenantOperator(tenant)); } /** Returns the list of {@link ApplicationRole}s a {@link UserId} may be a member of. */ - public List<ApplicationRole> applicationRoles(TenantName tenant, ApplicationName application) { - return List.of(roles.applicationAdmin(tenant, application), - roles.applicationOperator(tenant, application), - roles.applicationDeveloper(tenant, application), - roles.applicationReader(tenant, application)); + public static List<ApplicationRole> applicationRoles(TenantName tenant, ApplicationName application) { + return List.of(Role.applicationAdmin(tenant, application), + Role.applicationOperator(tenant, application), + Role.applicationDeveloper(tenant, application), + Role.applicationReader(tenant, application)); } /** Returns the {@link Role} the given value represents. */ - public Role toRole(String value) { + public static Role toRole(String value) { String[] parts = value.split("\\."); - if (parts.length == 1) return toOperatorRole(parts[0]); + if (parts.length == 1 && parts[0].equals("hostedOperator")) return Role.hostedOperator(); if (parts.length == 2) return toRole(TenantName.from(parts[0]), parts[1]); if (parts.length == 3) return toRole(TenantName.from(parts[0]), ApplicationName.from(parts[1]), parts[2]); throw new IllegalArgumentException("Malformed or illegal role value '" + value + "'."); } - public Role toOperatorRole(String roleName) { - switch (roleName) { - case "hostedOperator": return roles.hostedOperator(); - default: throw new IllegalArgumentException("Malformed or illegal role name '" + roleName + "'."); - } - } - /** Returns the {@link Role} the given tenant, application and role names correspond to. */ - public Role toRole(TenantName tenant, String roleName) { + public static Role toRole(TenantName tenant, String roleName) { switch (roleName) { - case "tenantOwner": return roles.tenantOwner(tenant); - case "tenantAdmin": return roles.tenantAdmin(tenant); - case "tenantOperator": return roles.tenantOperator(tenant); + case "tenantOwner": return Role.tenantOwner(tenant); + case "tenantAdmin": return Role.tenantAdmin(tenant); + case "tenantOperator": return Role.tenantOperator(tenant); default: throw new IllegalArgumentException("Malformed or illegal role name '" + roleName + "'."); } } /** Returns the {@link Role} the given tenant and role names correspond to. */ - public Role toRole(TenantName tenant, ApplicationName application, String roleName) { + public static Role toRole(TenantName tenant, ApplicationName application, String roleName) { switch (roleName) { - case "applicationAdmin": return roles.applicationAdmin(tenant, application); - case "applicationOperator": return roles.applicationOperator(tenant, application); - case "applicationDeveloper": return roles.applicationDeveloper(tenant, application); - case "applicationReader": return roles.applicationReader(tenant, application); + case "applicationAdmin": return Role.applicationAdmin(tenant, application); + case "applicationOperator": return Role.applicationOperator(tenant, application); + case "applicationDeveloper": return Role.applicationDeveloper(tenant, application); + case "applicationReader": return Role.applicationReader(tenant, application); default: throw new IllegalArgumentException("Malformed or illegal role name '" + roleName + "'."); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Action.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Action.java index 2d9ef25d1f5..0aa6cfd1f84 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Action.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Action.java @@ -18,11 +18,11 @@ public enum Action { update, delete; - public static EnumSet<Action> all() { + static EnumSet<Action> all() { return EnumSet.allOf(Action.class); } - public static EnumSet<Action> write() { + static EnumSet<Action> write() { return EnumSet.of(create, update, delete); } 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 index cc1e8462580..37ef06d461b 100644 --- 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 @@ -5,14 +5,14 @@ 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}. + * A {@link Role} with a {@link Context} of a {@link TenantName} and an {@link ApplicationName}. * * @author jonmv */ public class ApplicationRole extends Role { - ApplicationRole(RoleDefinition roleDefinition, SystemName system, TenantName tenant, ApplicationName application) { - super(roleDefinition, Context.limitedTo(tenant, application, system)); + ApplicationRole(RoleDefinition roleDefinition, TenantName tenant, ApplicationName application) { + super(roleDefinition, Context.limitedTo(tenant, application)); } /** Returns the {@link TenantName} this is bound to. */ diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Context.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Context.java index 14d8d06d0c6..1ca36b75cf5 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Context.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Context.java @@ -13,46 +13,39 @@ import java.util.Optional; * * @author mpolden */ -public class Context { +class Context { private final Optional<TenantName> tenant; private final Optional<ApplicationName> application; - private final SystemName system; - private Context(Optional<TenantName> tenant, Optional<ApplicationName> application, SystemName system) { + private Context(Optional<TenantName> tenant, Optional<ApplicationName> application) { this.tenant = Objects.requireNonNull(tenant, "tenant must be non-null"); this.application = Objects.requireNonNull(application, "application must be non-null"); - this.system = Objects.requireNonNull(system, "system must be non-null"); } /** A specific tenant this is valid for, if any */ - public Optional<TenantName> tenant() { + Optional<TenantName> tenant() { return tenant; } /** A specific application this is valid for, if any */ - public Optional<ApplicationName> application() { + Optional<ApplicationName> application() { return application; } - /** System in which this is valid */ - public SystemName system() { - return system; + /** Returns a context that has no restrictions on tenant or application */ + static Context unlimited() { + return new Context(Optional.empty(), Optional.empty()); } - /** Returns a context that has no restrictions on tenant or application in given system */ - public static Context unlimitedIn(SystemName system) { - return new Context(Optional.empty(), Optional.empty(), system); + /** Returns a context that is limited to given tenant */ + static Context limitedTo(TenantName tenant) { + return new Context(Optional.of(tenant), Optional.empty()); } - /** Returns a context that is limited to given tenant and system */ - public static Context limitedTo(TenantName tenant, SystemName system) { - return new Context(Optional.of(tenant), Optional.empty(), system); - } - - /** Returns a context that is limited to given tenant, application and system */ - public static Context limitedTo(TenantName tenant, ApplicationName application, SystemName system) { - return new Context(Optional.of(tenant), Optional.of(application), system); + /** Returns a context that is limited to given tenant, application */ + static Context limitedTo(TenantName tenant, ApplicationName application) { + return new Context(Optional.of(tenant), Optional.of(application)); } @Override @@ -61,19 +54,18 @@ public class Context { if (o == null || getClass() != o.getClass()) return false; Context context = (Context) o; return tenant.equals(context.tenant) && - application.equals(context.application) && - system == context.system; + application.equals(context.application); } @Override public int hashCode() { - return Objects.hash(tenant, application, system); + return Objects.hash(tenant, application); } @Override public String toString() { return "tenant " + tenant.map(TenantName::value).orElse("[none]") + ", application " + - application.map(ApplicationName::value).orElse("[none]") + ", system " + system; + application.map(ApplicationName::value).orElse("[none]"); } } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Enforcer.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Enforcer.java new file mode 100644 index 00000000000..c83e25c6073 --- /dev/null +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Enforcer.java @@ -0,0 +1,25 @@ +package com.yahoo.vespa.hosted.controller.api.role; + +import com.yahoo.config.provision.SystemName; + +import java.net.URI; + +/** + * Checks whether {@link Role}s have the required {@link Privilege}s to perform {@link Action}s on given {@link java.net.URI}s. + * + * @author jonmv + */ +public class Enforcer { + + private final SystemName system; + + public Enforcer(SystemName system) { + this.system = system; + } + + /** Returns whether {@code role} has permission to perform {@code action} on {@code resource}, in this enforcer's system. */ + public boolean allows(Role role, Action action, URI resource) { + return role.definition().policies().stream().anyMatch(policy -> policy.evaluate(action, resource, role.context, system)); + } + +} diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java index 23bf8514b9c..c64d1f9f89b 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java @@ -17,7 +17,7 @@ import java.util.Set; * @author mpolden * @author jonmv */ -public enum PathGroup { +enum PathGroup { /** Paths used for system management by operators. */ operator("/controller/v1/{*}", @@ -170,12 +170,12 @@ public enum PathGroup { } /** All known path groups */ - public static Set<PathGroup> all() { + static Set<PathGroup> all() { return EnumSet.allOf(PathGroup.class); } /** Returns whether this group matches path in given context */ - public boolean matches(URI uri, Context context) { + boolean matches(URI uri, Context context) { return get(uri).map(p -> { boolean match = true; String tenant = p.get(Matcher.tenant.name); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java index db4cca20b9a..15745d69dc5 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java @@ -11,12 +11,12 @@ import java.util.Set; /** * Policies for REST APIs in the controller. A policy is only considered when defined in a {@link Role}. * A policy describes a set of {@link Privilege}s, which are valid for a set of {@link SystemName}s. - * A policy is evaluated with a {@link Context}, which provides the {@link SystemName} the policy is - * evaluated in, and any limitations to a specific {@link TenantName} or {@link ApplicationName}. + * A policy is evaluated by an {@link Enforcer}, which holds the {@link SystemName} the evaluation is done in. + * A policy is evaluated with a {@link Context}, which may limit it to a specific {@link TenantName} or {@link ApplicationName}. * * @author mpolden */ -public enum Policy { +enum Policy { /** Full access to everything. */ operator(Privilege.grant(Action.all()) @@ -125,9 +125,9 @@ public enum Policy { } /** Returns whether action is allowed on path in given context */ - public boolean evaluate(Action action, URI uri, Context context) { + boolean evaluate(Action action, URI uri, Context context, SystemName system) { return privileges.stream().anyMatch(privilege -> privilege.actions().contains(action) && - privilege.systems().contains(context.system()) && + privilege.systems().contains(system) && privilege.pathGroups().stream() .anyMatch(pg -> pg.matches(uri, context))); } diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Privilege.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Privilege.java index a53717b25d6..896b22a8676 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Privilege.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Privilege.java @@ -14,7 +14,7 @@ import java.util.Set; * * @author mpolden */ -public class Privilege { +class Privilege { private final Set<SystemName> systems; private final Set<Action> actions; @@ -30,17 +30,17 @@ public class Privilege { } /** Systems where this applies */ - public Set<SystemName> systems() { + Set<SystemName> systems() { return systems; } /** Actions allowed by this */ - public Set<Action> actions() { + Set<Action> actions() { return actions; } /** Path groups where this applies */ - public Set<PathGroup> pathGroups() { + Set<PathGroup> pathGroups() { return pathGroups; } @@ -59,15 +59,15 @@ public class Privilege { return Objects.hash(systems, actions, pathGroups); } - public static PrivilegeBuilder grant(Action... actions) { + static PrivilegeBuilder grant(Action... actions) { return grant(Set.of(actions)); } - public static PrivilegeBuilder grant(Set<Action> actions) { + static PrivilegeBuilder grant(Set<Action> actions) { return new PrivilegeBuilder(actions); } - public static class PrivilegeBuilder { + static class PrivilegeBuilder { private Set<Action> actions; private Set<PathGroup> pathGroups; @@ -77,20 +77,20 @@ public class Privilege { this.pathGroups = new LinkedHashSet<>(); } - public PrivilegeBuilder on(PathGroup... pathGroups) { + PrivilegeBuilder on(PathGroup... pathGroups) { return on(Set.of(pathGroups)); } - public PrivilegeBuilder on(Set<PathGroup> pathGroups) { + PrivilegeBuilder on(Set<PathGroup> pathGroups) { this.pathGroups.addAll(pathGroups); return this; } - public Privilege in(SystemName... systems) { + Privilege in(SystemName... systems) { return in(Set.of(systems)); } - public Privilege in(Set<SystemName> systems) { + Privilege in(Set<SystemName> systems) { return new Privilege(systems, actions, pathGroups); } 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 c28fa7a3fc3..f36107db228 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 @@ -1,12 +1,15 @@ // 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.TenantName; + import java.net.URI; import java.util.Objects; /** * A role is a combination of a {@link RoleDefinition} and a {@link Context}, which allows evaluation - * of access control for a given action on a resource. Create using {@link Roles}. + * of access control for a given action on a resource. * * @author jonmv */ @@ -20,19 +23,71 @@ public abstract class Role { this.context = Objects.requireNonNull(context); } - /** Returns the role definition of this bound role. */ - public RoleDefinition definition() { return roleDefinition; } + /** Returns a {@link RoleDefinition#hostedOperator} for the current system. */ + public static UnboundRole hostedOperator() { + return new UnboundRole(RoleDefinition.hostedOperator); + } + + /** Returns a {@link RoleDefinition#everyone} for the current system. */ + public static UnboundRole everyone() { + return new UnboundRole(RoleDefinition.everyone); + } + + /** Returns a {@link RoleDefinition#athenzTenantAdmin} for the current system and given tenant. */ + public static TenantRole athenzTenantAdmin(TenantName tenant) { + return new TenantRole(RoleDefinition.athenzTenantAdmin, tenant); + } + + /** Returns a {@link RoleDefinition#tenantPipeline} for the current system and given tenant and application. */ + public static ApplicationRole tenantPipeline(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.tenantPipeline, tenant, application); + } + + /** Returns a {@link RoleDefinition#tenantOwner} for the current system and given tenant. */ + public static TenantRole tenantOwner(TenantName tenant) { + return new TenantRole(RoleDefinition.tenantOwner, tenant); + } + + /** Returns a {@link RoleDefinition#tenantAdmin} for the current system and given tenant. */ + public static TenantRole tenantAdmin(TenantName tenant) { + return new TenantRole(RoleDefinition.tenantAdmin, tenant); + } - /** Returns whether this role is allowed to perform the given action on the given resource. */ - public final boolean allows(Action action, URI uri) { - return roleDefinition.policies().stream().anyMatch(policy -> policy.evaluate(action, uri, context)); + /** Returns a {@link RoleDefinition#tenantOperator} for the current system and given tenant. */ + public static TenantRole tenantOperator(TenantName tenant) { + return new TenantRole(RoleDefinition.tenantOperator, tenant); } + /** Returns a {@link RoleDefinition#applicationAdmin} for the current system and given tenant and application. */ + public static ApplicationRole applicationAdmin(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.applicationAdmin, tenant, application); + } + + /** Returns a {@link RoleDefinition#applicationOperator} for the current system and given tenant and application. */ + public static ApplicationRole applicationOperator(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.applicationOperator, tenant, application); + } + + /** Returns a {@link RoleDefinition#applicationDeveloper} for the current system and given tenant and application. */ + public static ApplicationRole applicationDeveloper(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.applicationDeveloper, tenant, application); + } + + /** Returns a {@link RoleDefinition#applicationReader} for the current system and given tenant and application. */ + public static ApplicationRole applicationReader(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.applicationReader, tenant, application); + } + + /** Returns a {@link RoleDefinition#buildService} for the current system and given tenant and application. */ + public static ApplicationRole buildService(TenantName tenant, ApplicationName application) { + return new ApplicationRole(RoleDefinition.buildService, tenant, application); + } + + /** Returns the role definition of this bound role. */ + public RoleDefinition definition() { return roleDefinition; } + /** Returns whether the other role is a parent of this, and has a context included in this role's context. */ public boolean implies(Role other) { - if ( ! context.system().equals(other.context.system())) - throw new IllegalStateException("Coexisting roles should always be in the same system."); - return (context.tenant().isEmpty() || context.tenant().equals(other.context.tenant())) && (context.application().isEmpty() || context.application().equals(other.context.application())) && roleDefinition.inherited().contains(other.roleDefinition); 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 deleted file mode 100644 index bfdb0be4378..00000000000 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Roles.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.yahoo.vespa.hosted.controller.api.role; - -import com.google.inject.Inject; -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; - -import java.util.Objects; - -/** - * Use if you need to create {@link Role}s for its system. - * - * This also defines the relationship between {@link RoleDefinition}s and their required {@link Context}s. - * - * @author jonmv - */ -public class Roles { - - private final SystemName system; - - @Inject - public Roles(ZoneRegistry zones) { - this(zones.system()); - } - - /** Creates 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 RoleDefinition#hostedOperator} for the current system. */ - public UnboundRole hostedOperator() { - return new UnboundRole(RoleDefinition.hostedOperator, system); - } - - /** Returns a {@link RoleDefinition#everyone} for the current system. */ - public UnboundRole everyone() { - return new UnboundRole(RoleDefinition.everyone, system); - } - - - // Athenz based roles. - /** Returns a {@link RoleDefinition#athenzTenantAdmin} for the current system and given tenant. */ - public TenantRole athenzTenantAdmin(TenantName tenant) { - return new TenantRole(RoleDefinition.athenzTenantAdmin, system, tenant); - } - - /** Returns a {@link RoleDefinition#tenantPipeline} for the current system and given tenant and application. */ - public ApplicationRole tenantPipeline(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.tenantPipeline, system, tenant, application); - } - - - // Other identity provider based roles. - /** Returns a {@link RoleDefinition#tenantOwner} for the current system and given tenant. */ - public TenantRole tenantOwner(TenantName tenant) { - return new TenantRole(RoleDefinition.tenantOwner, system, tenant); - } - - /** Returns a {@link RoleDefinition#tenantAdmin} for the current system and given tenant. */ - public TenantRole tenantAdmin(TenantName tenant) { - return new TenantRole(RoleDefinition.tenantAdmin, system, tenant); - } - - /** Returns a {@link RoleDefinition#tenantOperator} for the current system and given tenant. */ - public TenantRole tenantOperator(TenantName tenant) { - return new TenantRole(RoleDefinition.tenantOperator, system, tenant); - } - - /** Returns a {@link RoleDefinition#applicationAdmin} for the current system and given tenant and application. */ - public ApplicationRole applicationAdmin(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.applicationAdmin, system, tenant, application); - } - - /** Returns a {@link RoleDefinition#applicationOperator} for the current system and given tenant and application. */ - public ApplicationRole applicationOperator(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.applicationOperator, system, tenant, application); - } - - /** Returns a {@link RoleDefinition#applicationDeveloper} for the current system and given tenant and application. */ - public ApplicationRole applicationDeveloper(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.applicationDeveloper, system, tenant, application); - } - - /** Returns a {@link RoleDefinition#applicationReader} for the current system and given tenant and application. */ - public ApplicationRole applicationReader(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.applicationReader, system, tenant, application); - } - - /** Returns a {@link RoleDefinition#buildService} for the current system and given tenant and application. */ - public ApplicationRole buildService(TenantName tenant, ApplicationName application) { - return new ApplicationRole(RoleDefinition.buildService, system, tenant, application); - } - -} 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 index 134628ec3a3..8712b436c0d 100644 --- 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 @@ -4,14 +4,14 @@ 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}. + * A {@link Role} with a {@link Context} of a {@link TenantName}. * * @author jonmv */ public class TenantRole extends Role { - TenantRole(RoleDefinition roleDefinition, SystemName system, TenantName tenant) { - super(roleDefinition, Context.limitedTo(tenant, system)); + TenantRole(RoleDefinition roleDefinition, TenantName tenant) { + super(roleDefinition, Context.limitedTo(tenant)); } /** Returns the {@link TenantName} this is bound to. */ 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 index eb8319b2012..93774b25210 100644 --- 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 @@ -3,14 +3,14 @@ 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}. + * A {@link Role} with an unlimited {@link Context}. * * @author jonmv */ public class UnboundRole extends Role { - UnboundRole(RoleDefinition roleDefinition, SystemName system) { - super(roleDefinition, Context.unlimitedIn(system)); + UnboundRole(RoleDefinition roleDefinition) { + super(roleDefinition, Context.unlimited()); } @Override diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/RolesTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/RolesTest.java new file mode 100644 index 00000000000..4c7fe57a6d8 --- /dev/null +++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/RolesTest.java @@ -0,0 +1,65 @@ +package com.yahoo.vespa.hosted.controller.api.integration.user; + +import com.yahoo.config.provision.ApplicationName; +import com.yahoo.config.provision.TenantName; +import com.yahoo.vespa.hosted.controller.api.role.ApplicationRole; +import com.yahoo.vespa.hosted.controller.api.role.Role; +import com.yahoo.vespa.hosted.controller.api.role.TenantRole; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author jonmv + */ +public class RolesTest { + + @Test + public void testSerialization() { + TenantName tenant = TenantName.from("my-tenant"); + for (TenantRole role : Roles.tenantRoles(tenant)) + assertEquals(role, Roles.toRole(Roles.valueOf(role))); + + ApplicationName application = ApplicationName.from("my-application"); + for (ApplicationRole role : Roles.applicationRoles(tenant, application)) + assertEquals(role, Roles.toRole(Roles.valueOf(role))); + + assertEquals(Role.hostedOperator(), + Roles.toRole("hostedOperator")); + assertEquals(Role.tenantOperator(tenant), + Roles.toRole("my-tenant.tenantOperator")); + assertEquals(Role.applicationReader(tenant, application), + Roles.toRole("my-tenant.my-application.applicationReader")); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalTenantName() { + Roles.valueOf(Role.tenantAdmin(TenantName.from("my.tenant"))); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalApplicationName() { + Roles.valueOf(Role.applicationOperator(TenantName.from("my-tenant"), ApplicationName.from("my.app"))); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalRole() { + Roles.valueOf(Role.tenantPipeline(TenantName.from("my-tenant"), ApplicationName.from("my-app"))); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalRoleValue() { + Roles.toRole("my-tenant.awesomePerson"); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalCombination() { + Roles.toRole("my-tenant.my-application.tenantOwner"); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalValue() { + Roles.toRole("everyone"); + } + +} diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/UserRolesTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/UserRolesTest.java deleted file mode 100644 index 89df7a24559..00000000000 --- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/integration/user/UserRolesTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.yahoo.vespa.hosted.controller.api.integration.user; - -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.SystemName; -import com.yahoo.config.provision.TenantName; -import com.yahoo.vespa.hosted.controller.api.role.ApplicationRole; -import com.yahoo.vespa.hosted.controller.api.role.Roles; -import com.yahoo.vespa.hosted.controller.api.role.TenantRole; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * @author jonmv - */ -public class UserRolesTest { - - private static final Roles roles = new Roles(SystemName.main); - private static final UserRoles userRoles = new UserRoles(roles); - - @Test - public void testSerialization() { - TenantName tenant = TenantName.from("my-tenant"); - for (TenantRole role : userRoles.tenantRoles(tenant)) - assertEquals(role, userRoles.toRole(UserRoles.valueOf(role))); - - ApplicationName application = ApplicationName.from("my-application"); - for (ApplicationRole role : userRoles.applicationRoles(tenant, application)) - assertEquals(role, userRoles.toRole(UserRoles.valueOf(role))); - - assertEquals(roles.tenantOperator(tenant), - userRoles.toRole("my-tenant.tenantOperator")); - assertEquals(roles.applicationReader(tenant, application), - userRoles.toRole("my-tenant.my-application.applicationReader")); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalTenantName() { - UserRoles.valueOf(roles.tenantAdmin(TenantName.from("my.tenant"))); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalApplicationName() { - UserRoles.valueOf(roles.applicationOperator(TenantName.from("my-tenant"), ApplicationName.from("my.app"))); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalRole() { - UserRoles.valueOf(roles.tenantPipeline(TenantName.from("my-tenant"), ApplicationName.from("my-app"))); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalRoleValue() { - userRoles.toRole("my-tenant.awesomePerson"); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalCombination() { - userRoles.toRole("my-tenant.my-application.tenantOwner"); - } - - @Test(expected = IllegalArgumentException.class) - public void illegalValue() { - userRoles.toRole("everyone"); - } - - @Test - public void allowHostedOperator() { - assertEquals(roles.hostedOperator(), userRoles.toRole("hostedOperator")); - } - -} 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 index 6cfe01cfb77..4c11da3b697 100644 --- 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 @@ -16,59 +16,61 @@ import static org.junit.Assert.assertTrue; */ public class RoleTest { + private static final Enforcer mainEnforcer = new Enforcer(SystemName.main); + private static final Enforcer vaasEnforcer = new Enforcer(SystemName.vaas); + @Test public void operator_membership() { - Role role = new Roles(SystemName.main).hostedOperator(); + Role role = Role.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"))); + assertFalse(mainEnforcer.allows(role, Action.create, URI.create("/not/explicitly/defined"))); + assertTrue(mainEnforcer.allows(role, Action.create, URI.create("/controller/v1/foo"))); + assertTrue(mainEnforcer.allows(role, Action.update, URI.create("/os/v1/bar"))); + assertTrue(mainEnforcer.allows(role, Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); + assertTrue(mainEnforcer.allows(role, 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"))); + Role role = Role.athenzTenantAdmin(TenantName.from("t1")); + assertFalse(mainEnforcer.allows(role, Action.create, URI.create("/not/explicitly/defined"))); + assertFalse("Deny access to operator API", mainEnforcer.allows(role, Action.create, URI.create("/controller/v1/foo"))); + assertFalse("Deny access to other tenant and app", mainEnforcer.allows(role, Action.update, URI.create("/application/v4/tenant/t2/application/a2"))); + assertTrue(mainEnforcer.allows(role, Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); + + Role publicSystem = Role.athenzTenantAdmin(TenantName.from("t1")); + assertFalse(vaasEnforcer.allows(publicSystem, Action.read, URI.create("/controller/v1/foo"))); + assertTrue(vaasEnforcer.allows(publicSystem, Action.read, URI.create("/badge/v1/badge"))); + assertTrue(vaasEnforcer.allows(publicSystem, 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"))); + Role role = Role.tenantPipeline(TenantName.from("t1"), ApplicationName.from("a1")); + assertFalse(vaasEnforcer.allows(role, Action.create, URI.create("/not/explicitly/defined"))); + assertFalse(vaasEnforcer.allows(role, Action.update, URI.create("/application/v4/tenant/t1/application/a1"))); + assertTrue(vaasEnforcer.allows(role, Action.create, URI.create("/application/v4/tenant/t1/application/a1/jobreport"))); + assertFalse("No global read access", vaasEnforcer.allows(role, Action.read, URI.create("/controller/v1/foo"))); } @Test public void implications() { - Roles roles = new Roles(SystemName.main); TenantName tenant1 = TenantName.from("t1"); ApplicationName application1 = ApplicationName.from("a1"); TenantName tenant2 = TenantName.from("t2"); ApplicationName application2 = ApplicationName.from("a2"); - Role tenantOwner1 = roles.tenantOwner(tenant1); - Role tenantAdmin1 = roles.tenantAdmin(tenant1); - Role tenantAdmin2 = roles.tenantAdmin(tenant2); - Role tenantOperator1 = roles.tenantOperator(tenant1); - Role applicationAdmin11 = roles.applicationAdmin(tenant1, application1); - Role applicationOperator11 = roles.applicationOperator(tenant1, application1); - Role applicationDeveloper11 = roles.applicationDeveloper(tenant1, application1); - Role applicationReader11 = roles.applicationReader(tenant1, application1); - Role applicationReader12 = roles.applicationReader(tenant1, application2); - Role applicationReader22 = roles.applicationReader(tenant2, application2); + Role tenantOwner1 = Role.tenantOwner(tenant1); + Role tenantAdmin1 = Role.tenantAdmin(tenant1); + Role tenantAdmin2 = Role.tenantAdmin(tenant2); + Role tenantOperator1 = Role.tenantOperator(tenant1); + Role applicationAdmin11 = Role.applicationAdmin(tenant1, application1); + Role applicationOperator11 = Role.applicationOperator(tenant1, application1); + Role applicationDeveloper11 = Role.applicationDeveloper(tenant1, application1); + Role applicationReader11 = Role.applicationReader(tenant1, application1); + Role applicationReader12 = Role.applicationReader(tenant1, application2); + Role applicationReader22 = Role.applicationReader(tenant2, application2); assertFalse(tenantOwner1.implies(tenantOwner1)); assertTrue(tenantOwner1.implies(tenantAdmin1)); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java index 9886e5c1329..365b7960958 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Controller.java @@ -23,11 +23,10 @@ import com.yahoo.vespa.hosted.controller.api.integration.github.GitHub; import com.yahoo.vespa.hosted.controller.api.integration.organization.Mailer; import com.yahoo.vespa.hosted.controller.api.integration.routing.RoutingGenerator; import com.yahoo.config.provision.zone.ZoneId; -import com.yahoo.vespa.hosted.controller.api.integration.user.UserRoles; +import com.yahoo.vespa.hosted.controller.api.integration.user.Roles; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.api.role.ApplicationRole; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.api.role.TenantRole; import com.yahoo.vespa.hosted.controller.auditlog.AuditLogger; import com.yahoo.vespa.hosted.controller.deployment.JobController; @@ -82,7 +81,6 @@ public class Controller extends AbstractComponent { private final Mailer mailer; private final AuditLogger auditLogger; private final FlagSource flagSource; - private final UserRoles roles; /** * Creates a controller @@ -135,7 +133,6 @@ public class Controller extends AbstractComponent { ); tenantController = new TenantController(this, curator, accessControl); auditLogger = new AuditLogger(curator, clock); - roles = new UserRoles(new Roles(zoneRegistry.system())); // Record the version of this controller curator().writeControllerVersion(this.hostname(), Vtag.currentVersion); @@ -298,16 +295,16 @@ public class Controller extends AbstractComponent { /** Returns all other roles the given tenant role implies. */ public Set<Role> impliedRoles(TenantRole role) { - return Stream.concat(roles.tenantRoles(role.tenant()).stream(), + return Stream.concat(Roles.tenantRoles(role.tenant()).stream(), applications().asList(role.tenant()).stream() - .flatMap(application -> roles.applicationRoles(application.id().tenant(), application.id().application()).stream())) + .flatMap(application -> Roles.applicationRoles(application.id().tenant(), application.id().application()).stream())) .filter(role::implies) .collect(Collectors.toUnmodifiableSet()); } /** Returns all other roles the given application role implies. */ public Set<Role> impliedRoles(ApplicationRole role) { - return roles.applicationRoles(role.tenant(), role.application()).stream() + return Roles.applicationRoles(role.tenant(), role.application()).stream() .filter(role::implies) .collect(Collectors.toUnmodifiableSet()); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java index 8e397366203..15cdf034ca0 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java @@ -17,7 +17,6 @@ import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.TenantController; import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.athenz.impl.AthenzFacade; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; @@ -44,14 +43,12 @@ public class AthenzRoleFilter extends CorsRequestFilterBase { // TODO: No need f private final AthenzFacade athenz; private final TenantController tenants; - private final Roles roles; @Inject public AthenzRoleFilter(CorsFilterConfig config, AthenzClientFactory athenzClientFactory, Controller controller) { super(Set.copyOf(config.allowedUrls())); this.athenz = new AthenzFacade(athenzClientFactory); this.tenants = controller.tenants(); - this.roles = new Roles(controller.system()); } @Override @@ -80,18 +77,18 @@ public class AthenzRoleFilter extends CorsRequestFilterBase { // TODO: No need f AthenzIdentity identity = principal.getIdentity(); if (athenz.hasHostedOperatorAccess(identity)) - return Set.of(roles.hostedOperator()); + return Set.of(Role.hostedOperator()); if (tenant.isPresent() && isTenantAdmin(identity, tenant.get())) - return Set.of(roles.athenzTenantAdmin(tenant.get().name())); + return Set.of(Role.athenzTenantAdmin(tenant.get().name())); if (identity.getDomain().equals(SCREWDRIVER_DOMAIN) && application.isPresent() && tenant.isPresent()) // NOTE: Only fine-grained deploy authorization for Athenz tenants if ( tenant.get().type() != Tenant.Type.athenz || hasDeployerAccess(identity, ((AthenzTenant) tenant.get()).domain(), application.get())) - return Set.of(roles.tenantPipeline(tenant.get().name(), application.get())); + return Set.of(Role.tenantPipeline(tenant.get().name(), application.get())); - return Set.of(roles.everyone()); + return Set.of(Role.everyone()); } private boolean isTenantAdmin(AthenzIdentity identity, Tenant tenant) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java index 181731ef896..d07ad1ca907 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilter.java @@ -11,11 +11,10 @@ import com.yahoo.jdisc.http.filter.security.cors.CorsRequestFilterBase; import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.role.Action; +import com.yahoo.vespa.hosted.controller.api.role.Enforcer; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; -import java.security.Principal; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; @@ -29,7 +28,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { private static final Logger log = Logger.getLogger(ControllerAuthorizationFilter.class.getName()); - private final Roles roles; + private final Enforcer enforcer; @Inject public ControllerAuthorizationFilter(Controller controller, @@ -40,7 +39,7 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { ControllerAuthorizationFilter(SystemName system, Set<String> allowedUrls) { super(allowedUrls); - this.roles = new Roles(system); + this.enforcer = new Enforcer(system); } @Override @@ -54,11 +53,11 @@ public class ControllerAuthorizationFilter extends CorsRequestFilterBase { Action action = Action.from(HttpRequest.Method.valueOf(request.getMethod())); // Avoid expensive look-ups when request is always legal. - if (roles.everyone().allows(action, request.getUri())) + if (enforcer.allows(Role.everyone(), action, request.getUri())) return Optional.empty(); Set<Role> roles = securityContext.get().roles(); - if (roles.stream().anyMatch(role -> role.allows(action, request.getUri()))) + if (roles.stream().anyMatch(role -> enforcer.allows(role, action, request.getUri()))) return Optional.empty(); } catch (Exception e) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java index b8c904a80f6..5ef997b6d55 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiHandler.java @@ -15,10 +15,9 @@ import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.api.integration.user.UserId; import com.yahoo.vespa.hosted.controller.api.integration.user.UserManagement; -import com.yahoo.vespa.hosted.controller.api.integration.user.UserRoles; +import com.yahoo.vespa.hosted.controller.api.integration.user.Roles; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.api.role.RoleDefinition; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; import com.yahoo.vespa.hosted.controller.restapi.MessageResponse; import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse; @@ -46,13 +45,11 @@ public class UserApiHandler extends LoggingRequestHandler { private final static Logger log = Logger.getLogger(UserApiHandler.class.getName()); private static final String optionalPrefix = "/api"; - private final UserRoles roles; private final UserManagement users; @Inject - public UserApiHandler(Context parentCtx, Roles roles, UserManagement users) { + public UserApiHandler(Context parentCtx, UserManagement users) { super(parentCtx); - this.roles = new UserRoles(roles); this.users = users; } @@ -112,7 +109,7 @@ public class UserApiHandler extends LoggingRequestHandler { Cursor root = slime.setObject(); root.setString("tenant", tenantName); fillRoles(root, - roles.tenantRoles(TenantName.from(tenantName)), + Roles.tenantRoles(TenantName.from(tenantName)), Collections.emptyList()); return new SlimeJsonResponse(slime); } @@ -123,8 +120,8 @@ public class UserApiHandler extends LoggingRequestHandler { root.setString("tenant", tenantName); root.setString("application", applicationName); fillRoles(root, - roles.applicationRoles(TenantName.from(tenantName), ApplicationName.from(applicationName)), - roles.tenantRoles(TenantName.from(tenantName))); + Roles.applicationRoles(TenantName.from(tenantName), ApplicationName.from(applicationName)), + Roles.tenantRoles(TenantName.from(tenantName))); return new SlimeJsonResponse(slime); } @@ -159,7 +156,7 @@ public class UserApiHandler extends LoggingRequestHandler { Inspector requestObject = bodyInspector(request); String roleName = require("roleName", Inspector::asString, requestObject); UserId user = new UserId(require("user", Inspector::asString, requestObject)); - Role role = roles.toRole(TenantName.from(tenantName), roleName); + Role role = Roles.toRole(TenantName.from(tenantName), roleName); users.addUsers(role, List.of(user)); return new MessageResponse(user + " is now a member of " + role); } @@ -168,7 +165,7 @@ public class UserApiHandler extends LoggingRequestHandler { Inspector requestObject = bodyInspector(request); String roleName = require("roleName", Inspector::asString, requestObject); UserId user = new UserId(require("user", Inspector::asString, requestObject)); - Role role = roles.toRole(TenantName.from(tenantName), ApplicationName.from(applicationName), roleName); + Role role = Roles.toRole(TenantName.from(tenantName), ApplicationName.from(applicationName), roleName); users.addUsers(role, List.of(user)); return new MessageResponse(user + " is now a member of " + role); } @@ -177,7 +174,7 @@ public class UserApiHandler extends LoggingRequestHandler { Inspector requestObject = bodyInspector(request); String roleName = require("roleName", Inspector::asString, requestObject); UserId user = new UserId(require("user", Inspector::asString, requestObject)); - Role role = roles.toRole(TenantName.from(tenantName), roleName); + Role role = Roles.toRole(TenantName.from(tenantName), roleName); if ( role.definition() == RoleDefinition.tenantOwner && users.listUsers(role).equals(List.of(user))) throw new IllegalArgumentException("Can't remove the last owner of a tenant."); @@ -190,7 +187,7 @@ public class UserApiHandler extends LoggingRequestHandler { Inspector requestObject = bodyInspector(request); String roleName = require("roleName", Inspector::asString, requestObject); UserId user = new UserId(require("user", Inspector::asString, requestObject)); - Role role = roles.toRole(TenantName.from(tenantName), ApplicationName.from(applicationName), roleName); + Role role = Roles.toRole(TenantName.from(tenantName), ApplicationName.from(applicationName), roleName); users.removeUsers(role, List.of(user)); return new MessageResponse(user + " is no longer a member of " + role); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java index 008be2fd276..f803ab9f29c 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/security/CloudAccessControl.java @@ -7,10 +7,9 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.integration.organization.Marketplace; import com.yahoo.vespa.hosted.controller.api.integration.user.UserId; import com.yahoo.vespa.hosted.controller.api.integration.user.UserManagement; -import com.yahoo.vespa.hosted.controller.api.integration.user.UserRoles; +import com.yahoo.vespa.hosted.controller.api.integration.user.Roles; import com.yahoo.vespa.hosted.controller.api.role.ApplicationRole; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.api.role.TenantRole; import com.yahoo.vespa.hosted.controller.tenant.CloudTenant; import com.yahoo.vespa.hosted.controller.tenant.Tenant; @@ -25,15 +24,11 @@ public class CloudAccessControl implements AccessControl { private final Marketplace marketplace; private final UserManagement userManagement; - private final Roles roles; - private final UserRoles userRoles; @Inject - public CloudAccessControl(Marketplace marketplace, UserManagement userManagement, Roles roles) { + public CloudAccessControl(Marketplace marketplace, UserManagement userManagement) { this.marketplace = marketplace; this.userManagement = userManagement; - this.roles = roles; - this.userRoles = new UserRoles(roles); } @Override @@ -41,9 +36,9 @@ public class CloudAccessControl implements AccessControl { CloudTenantSpec spec = (CloudTenantSpec) tenantSpec; CloudTenant tenant = new CloudTenant(spec.tenant(), marketplace.resolveCustomer(spec.getRegistrationToken())); - for (Role role : userRoles.tenantRoles(spec.tenant())) + for (Role role : Roles.tenantRoles(spec.tenant())) userManagement.createRole(role); - userManagement.addUsers(roles.tenantOwner(spec.tenant()), List.of(new UserId(credentials.user().getName()))); + userManagement.addUsers(Role.tenantOwner(spec.tenant()), List.of(new UserId(credentials.user().getName()))); return tenant; } @@ -57,20 +52,20 @@ public class CloudAccessControl implements AccessControl { public void deleteTenant(TenantName tenant, Credentials credentials) { // Probably terminate customer subscription? - for (TenantRole role : userRoles.tenantRoles(tenant)) + for (TenantRole role : Roles.tenantRoles(tenant)) userManagement.deleteRole(role); } @Override public void createApplication(ApplicationId id, Credentials credentials) { - for (Role role : userRoles.applicationRoles(id.tenant(), id.application())) + for (Role role : Roles.applicationRoles(id.tenant(), id.application())) userManagement.createRole(role); - userManagement.addUsers(roles.applicationAdmin(id.tenant(), id.application()), List.of(new UserId(credentials.user().getName()))); + userManagement.addUsers(Role.applicationAdmin(id.tenant(), id.application()), List.of(new UserId(credentials.user().getName()))); } @Override public void deleteApplication(ApplicationId id, Credentials credentials) { - for (ApplicationRole role : userRoles.applicationRoles(id.tenant(), id.application())) + for (ApplicationRole role : Roles.applicationRoles(id.tenant(), id.application())) userManagement.deleteRole(role); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java index 95477758deb..4f068451d24 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerCloudTest.java @@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.restapi; import com.yahoo.application.container.handler.Request; import com.yahoo.config.provision.SystemName; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; import java.nio.charset.StandardCharsets; @@ -64,7 +63,7 @@ public class ControllerContainerCloudTest extends ControllerContainerTest { private final Request.Method method; private byte[] data = new byte[0]; private Principal user = () -> "user@test"; - private Set<Role> roles = Set.of(new Roles(system()).everyone()); + private Set<Role> roles = Set.of(Role.everyone()); private RequestBuilder(String path, Request.Method method) { this.path = path; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 6abfa7fa72d..4cb0d509531 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -96,7 +96,6 @@ public class ControllerContainerTest { " <component id='com.yahoo.vespa.hosted.controller.integration.ApplicationStoreMock'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockTesterCloud'/>\n" + " <component id='com.yahoo.vespa.hosted.controller.api.integration.stubs.MockMailer'/>\n" + - " <component id='com.yahoo.vespa.hosted.controller.api.role.Roles'/>\n" + " <handler id='com.yahoo.vespa.hosted.controller.restapi.deployment.DeploymentApiHandler'>\n" + " <binding>http://*/deployment/v1/*</binding>\n" + " </handler>\n" + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java index b48cb4bff50..e36a02f387c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java @@ -10,7 +10,7 @@ import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.role.Roles; +import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.athenz.HostedAthenzIdentities; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; @@ -66,55 +66,53 @@ public class AthenzRoleFilterTest { @Test public void testTranslations() { - Roles roles = new Roles(tester.controller().system()); - // Hosted operators are always members of the hostedOperator role. - assertEquals(Set.of(roles.hostedOperator()), + assertEquals(Set.of(Role.hostedOperator()), filter.roles(HOSTED_OPERATOR, NO_CONTEXT_PATH)); - assertEquals(Set.of(roles.hostedOperator()), + assertEquals(Set.of(Role.hostedOperator()), filter.roles(HOSTED_OPERATOR, TENANT_CONTEXT_PATH)); - assertEquals(Set.of(roles.hostedOperator()), + assertEquals(Set.of(Role.hostedOperator()), filter.roles(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH)); // Tenant admins are members of the athenzTenantAdmin role within their tenant subtree. - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_PIPELINE, NO_CONTEXT_PATH)); - assertEquals(Set.of(roles.athenzTenantAdmin(TENANT)), + assertEquals(Set.of(Role.athenzTenantAdmin(TENANT)), filter.roles(TENANT_ADMIN, TENANT_CONTEXT_PATH)); - assertEquals(Set.of(roles.athenzTenantAdmin(TENANT)), + assertEquals(Set.of(Role.athenzTenantAdmin(TENANT)), filter.roles(TENANT_ADMIN, APPLICATION_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_ADMIN, TENANT2_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_ADMIN, APPLICATION2_CONTEXT_PATH)); // Build services are members of the tenantPipeline role within their application subtree. - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_PIPELINE, NO_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_PIPELINE, TENANT_CONTEXT_PATH)); - assertEquals(Set.of(roles.tenantPipeline(TENANT, APPLICATION)), + assertEquals(Set.of(Role.tenantPipeline(TENANT, APPLICATION)), filter.roles(TENANT_PIPELINE, APPLICATION_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(TENANT_PIPELINE, APPLICATION2_CONTEXT_PATH)); // Unprivileged users are just members of the everyone role. - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(USER, NO_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(USER, TENANT_CONTEXT_PATH)); - assertEquals(Set.of(roles.everyone()), + assertEquals(Set.of(Role.everyone()), filter.roles(USER, APPLICATION_CONTEXT_PATH)); } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java index 105e10eefd2..f2b0039750e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/ControllerAuthorizationFilterTest.java @@ -7,7 +7,7 @@ import com.yahoo.config.provision.SystemName; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.vespa.hosted.controller.ControllerTester; -import com.yahoo.vespa.hosted.controller.api.role.Roles; +import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.api.role.SecurityContext; import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper; import org.junit.Test; @@ -34,8 +34,7 @@ public class ControllerAuthorizationFilterTest { @Test public void operator() { ControllerTester tester = new ControllerTester(); - Roles roles = new Roles(tester.controller().system()); - SecurityContext securityContext = new SecurityContext(() -> "operator", Set.of(roles.hostedOperator())); + SecurityContext securityContext = new SecurityContext(() -> "operator", Set.of(Role.hostedOperator())); ControllerAuthorizationFilter filter = createFilter(tester); assertIsAllowed(invokeFilter(filter, createRequest(Method.POST, "/zone/v2/path", securityContext))); @@ -46,8 +45,7 @@ public class ControllerAuthorizationFilterTest { @Test public void unprivileged() { ControllerTester tester = new ControllerTester(); - Roles roles = new Roles(tester.controller().system()); - SecurityContext securityContext = new SecurityContext(() -> "user", Set.of(roles.everyone())); + SecurityContext securityContext = new SecurityContext(() -> "user", Set.of(Role.everyone())); ControllerAuthorizationFilter filter = createFilter(tester); assertIsForbidden(invokeFilter(filter, createRequest(Method.POST, "/zone/v2/path", securityContext))); @@ -59,8 +57,7 @@ public class ControllerAuthorizationFilterTest { public void unprivilegedInPublic() { ControllerTester tester = new ControllerTester(); tester.zoneRegistry().setSystemName(SystemName.Public); - Roles roles = new Roles(tester.controller().system()); - SecurityContext securityContext = new SecurityContext(() -> "user", Set.of(roles.everyone())); + SecurityContext securityContext = new SecurityContext(() -> "user", Set.of(Role.everyone())); ControllerAuthorizationFilter filter = createFilter(tester); assertIsForbidden(invokeFilter(filter, createRequest(Method.POST, "/zone/v2/path", securityContext))); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java index 3a78e9fc262..59f63f0472a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java @@ -4,7 +4,6 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.SystemName; import com.yahoo.config.provision.TenantName; import com.yahoo.vespa.hosted.controller.api.role.Role; -import com.yahoo.vespa.hosted.controller.api.role.Roles; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerCloudTest; import org.junit.Test; @@ -28,8 +27,7 @@ public class UserApiTest extends ControllerContainerCloudTest { public void testUserManagement() { ContainerTester tester = new ContainerTester(container, responseFiles); assertEquals(SystemName.Public, tester.controller().system()); - Roles roles = new Roles(tester.controller().system()); - Set<Role> operator = Set.of(roles.hostedOperator()); + Set<Role> operator = Set.of(Role.hostedOperator()); ApplicationId id = ApplicationId.from("my-tenant", "my-app", "default"); @@ -70,80 +68,80 @@ public class UserApiTest extends ControllerContainerCloudTest { // POST a hosted operator role is not allowed. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(roles.tenantOwner(id.tenant()))) + .roles(Set.of(Role.tenantOwner(id.tenant()))) .data("{\"user\":\"evil@evil\",\"roleName\":\"hostedOperator\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'hostedOperator'.\"}", 400); // POST a tenant operator is available to the tenant owner. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(roles.tenantOwner(id.tenant()))) + .roles(Set.of(Role.tenantOwner(id.tenant()))) .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), "{\"message\":\"user 'operator@tenant' is now a member of role 'tenantOperator' of 'my-tenant'\"}"); // POST a tenant admin is not available to a tenant operator. tester.assertResponse(request("/user/v1/tenant/my-tenant", POST) - .roles(Set.of(roles.tenantOperator(id.tenant()))) + .roles(Set.of(Role.tenantOperator(id.tenant()))) .data("{\"user\":\"admin@tenant\",\"roleName\":\"tenantAdmin\"}"), accessDenied, 403); // POST an application admin for a non-existent application fails. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) - .roles(Set.of(roles.tenantOwner(TenantName.from("my-tenant")))) + .roles(Set.of(Role.tenantOwner(TenantName.from("my-tenant")))) .data("{\"user\":\"admin@app\",\"roleName\":\"applicationAdmin\"}"), "{\"error-code\":\"INTERNAL_SERVER_ERROR\",\"message\":\"NullPointerException\"}", 500); // POST an application is allowed for a tenant operator. tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", POST) .user("operator@tenant") - .roles(Set.of(roles.tenantOperator(id.tenant()))), + .roles(Set.of(Role.tenantOperator(id.tenant()))), new File("application-created.json")); // POST an application is not allowed under a different tenant. tester.assertResponse(request("/application/v4/tenant/other-tenant/application/my-app", POST) - .roles(Set.of(roles.tenantOperator(id.tenant()))), + .roles(Set.of(Role.tenantOperator(id.tenant()))), accessDenied, 403); // POST an application role is allowed for a tenant admin. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) - .roles(Set.of(roles.tenantAdmin(id.tenant()))) + .roles(Set.of(Role.tenantAdmin(id.tenant()))) .data("{\"user\":\"reader@app\",\"roleName\":\"applicationReader\"}"), "{\"message\":\"user 'reader@app' is now a member of role 'applicationReader' of 'my-app' owned by 'my-tenant'\"}"); // POST a tenant role is not allowed to an application. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", POST) - .roles(Set.of(roles.hostedOperator())) + .roles(Set.of(Role.hostedOperator())) .data("{\"user\":\"reader@app\",\"roleName\":\"tenantOperator\"}"), "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Malformed or illegal role name 'tenantOperator'.\"}", 400); // GET tenant role information is available to application readers. tester.assertResponse(request("/user/v1/tenant/my-tenant") - .roles(Set.of(roles.applicationReader(id.tenant(), id.application()))), + .roles(Set.of(Role.applicationReader(id.tenant(), id.application()))), new File("tenant-roles.json")); // GET application role information is available to tenant operators. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app") - .roles(Set.of(roles.tenantOperator(id.tenant()))), + .roles(Set.of(Role.tenantOperator(id.tenant()))), new File("application-roles.json")); // GET application role information is available also under the /api prefix. tester.assertResponse(request("/api/user/v1/tenant/my-tenant/application/my-app") - .roles(Set.of(roles.tenantOperator(id.tenant()))), + .roles(Set.of(Role.tenantOperator(id.tenant()))), new File("application-roles.json")); // DELETE an application role is allowed for an application admin. tester.assertResponse(request("/user/v1/tenant/my-tenant/application/my-app", DELETE) - .roles(Set.of(roles.applicationAdmin(id.tenant(), id.application()))) + .roles(Set.of(Role.applicationAdmin(id.tenant(), id.application()))) .data("{\"user\":\"operator@tenant\",\"roleName\":\"applicationAdmin\"}"), "{\"message\":\"user 'operator@tenant' is no longer a member of role 'applicationAdmin' of 'my-app' owned by 'my-tenant'\"}"); // DELETE an application is available to application admins. tester.assertResponse(request("/application/v4/tenant/my-tenant/application/my-app", DELETE) - .roles(Set.of(roles.applicationAdmin(id.tenant(), id.application()))), + .roles(Set.of(Role.applicationAdmin(id.tenant(), id.application()))), ""); // DELETE a tenant role is available to tenant admins. tester.assertResponse(request("/user/v1/tenant/my-tenant", DELETE) - .roles(Set.of(roles.tenantAdmin(id.tenant()))) + .roles(Set.of(Role.tenantAdmin(id.tenant()))) .data("{\"user\":\"operator@tenant\",\"roleName\":\"tenantOperator\"}"), "{\"message\":\"user 'operator@tenant' is no longer a member of role 'tenantOperator' of 'my-tenant'\"}"); @@ -155,7 +153,7 @@ public class UserApiTest extends ControllerContainerCloudTest { // DELETE the tenant is available to the tenant owner. tester.assertResponse(request("/application/v4/tenant/my-tenant", DELETE) - .roles(Set.of(roles.tenantOwner(id.tenant()))), + .roles(Set.of(Role.tenantOwner(id.tenant()))), new File("tenant-without-applications.json")); } |