From c4912794661e77d15debe08ea49aef27910dc27d Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 19 Jan 2022 13:44:28 +1100 Subject: [PATCH] Extract Role interface and allow multiple level of limiting (#81403) This PR extract an interface from the Role class. This helped to rework the LimitedRole class so it no longer has the constraint of one level of limiting. Resolves: #81192 Relates: #80117 --- .../xpack/core/security/authc/Subject.java | 28 ++-- .../authz/permission/LimitedRole.java | 93 +++++------ .../core/security/authz/permission/Role.java | 153 ++++++----------- .../security/authz/permission/SimpleRole.java | 158 ++++++++++++++++++ .../store/RoleReferenceIntersection.java | 52 ++++++ .../core/security/authc/SubjectTests.java | 19 ++- .../authz/permission/LimitedRoleTests.java | 30 ++-- .../store/RoleReferenceIntersectionTests.java | 79 +++++++++ .../authz/store/CompositeRolesStore.java | 23 +-- .../authz/store/CompositeRolesStoreTests.java | 5 +- 10 files changed, 427 insertions(+), 213 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersection.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersectionTests.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java index a48a7e3a105cd..e653a74bbf331 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java @@ -15,10 +15,10 @@ import org.elasticsearch.core.Nullable; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings; import org.elasticsearch.xpack.core.security.authz.store.RoleReference; +import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.User; -import java.util.List; import java.util.Map; import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES; @@ -85,27 +85,23 @@ public Map getMetadata() { return metadata; } - /** - * Return a List of RoleReferences that represents role definitions associated to the subject. - * The final role of this subject should be the intersection of all role references in the list. - */ - public List getRoleReferences(@Nullable AnonymousUser anonymousUser) { + public RoleReferenceIntersection getRoleReferenceIntersection(@Nullable AnonymousUser anonymousUser) { switch (type) { case USER: return buildRoleReferencesForUser(anonymousUser); case API_KEY: return buildRoleReferencesForApiKey(); case SERVICE_ACCOUNT: - return List.of(new RoleReference.ServiceAccountRoleReference(user.principal())); + return new RoleReferenceIntersection(new RoleReference.ServiceAccountRoleReference(user.principal())); default: assert false : "unknown subject type: [" + type + "]"; throw new IllegalStateException("unknown subject type: [" + type + "]"); } } - private List buildRoleReferencesForUser(AnonymousUser anonymousUser) { + private RoleReferenceIntersection buildRoleReferencesForUser(AnonymousUser anonymousUser) { if (user.equals(anonymousUser)) { - return List.of(new RoleReference.NamedRoleReference(user.roles())); + return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(user.roles())); } final String[] allRoleNames; if (anonymousUser == null || false == anonymousUser.enabled()) { @@ -117,10 +113,10 @@ private List buildRoleReferencesForUser(AnonymousUser anonymousUs } allRoleNames = ArrayUtils.concat(user.roles(), anonymousUser.roles()); } - return List.of(new RoleReference.NamedRoleReference(allRoleNames)); + return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(allRoleNames)); } - private List buildRoleReferencesForApiKey() { + private RoleReferenceIntersection buildRoleReferencesForApiKey() { if (version.before(VERSION_API_KEY_ROLES_AS_BYTES)) { return buildRolesReferenceForApiKeyBwc(); } @@ -136,9 +132,9 @@ private List buildRoleReferencesForApiKey() { RoleReference.ApiKeyRoleType.LIMITED_BY ); if (isEmptyRoleDescriptorsBytes(roleDescriptorsBytes)) { - return List.of(limitedByRoleReference); + return new RoleReferenceIntersection(limitedByRoleReference); } - return List.of( + return new RoleReferenceIntersection( new RoleReference.ApiKeyRoleReference(apiKeyId, roleDescriptorsBytes, RoleReference.ApiKeyRoleType.ASSIGNED), limitedByRoleReference ); @@ -148,7 +144,7 @@ private boolean isEmptyRoleDescriptorsBytes(BytesReference roleDescriptorsBytes) return roleDescriptorsBytes == null || (roleDescriptorsBytes.length() == 2 && "{}".equals(roleDescriptorsBytes.utf8ToString())); } - private List buildRolesReferenceForApiKeyBwc() { + private RoleReferenceIntersection buildRolesReferenceForApiKeyBwc() { final String apiKeyId = (String) metadata.get(AuthenticationField.API_KEY_ID_KEY); final Map roleDescriptorsMap = getRoleDescriptorMap(API_KEY_ROLE_DESCRIPTORS_KEY); final Map limitedByRoleDescriptorsMap = getRoleDescriptorMap(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY); @@ -161,9 +157,9 @@ private List buildRolesReferenceForApiKeyBwc() { RoleReference.ApiKeyRoleType.LIMITED_BY ); if (roleDescriptorsMap == null || roleDescriptorsMap.isEmpty()) { - return List.of(limitedByRoleReference); + return new RoleReferenceIntersection(limitedByRoleReference); } else { - return List.of( + return new RoleReferenceIntersection( new RoleReference.BwcApiKeyRoleReference(apiKeyId, roleDescriptorsMap, RoleReference.ApiKeyRoleType.ASSIGNED), limitedByRoleReference ); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java index e7d7f1ad57fca..1348aacd4af19 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java @@ -22,24 +22,30 @@ import java.util.Set; import java.util.function.Predicate; -// TODO: extract a Role interface so limitedRole can be more than 2 levels /** * A {@link Role} limited by another role.
* The effective permissions returned on {@link #authorize(String, Set, Map, FieldPermissionsCache)} call would be limited by the * provided role. */ -public final class LimitedRole extends Role { - private final Role limitedBy; - - LimitedRole( - ClusterPermission cluster, - IndicesPermission indices, - ApplicationPermission application, - RunAsPermission runAs, - Role limitedBy - ) { - super(Objects.requireNonNull(limitedBy, "limiting role is required").names(), cluster, indices, application, runAs); - this.limitedBy = limitedBy; +public final class LimitedRole implements Role { + private final Role baseRole; + private final Role limitedByRole; + + /** + * Create a new role defined by given role and the limited role. + * + * @param baseRole existing role {@link Role} + * @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role} + */ + public LimitedRole(Role baseRole, Role limitedByRole) { + this.baseRole = Objects.requireNonNull(baseRole); + this.limitedByRole = Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role"); + } + + @Override + public String[] names() { + // TODO: this is to retain existing behaviour, but it is not accurate + return limitedByRole.names(); } @Override @@ -64,7 +70,7 @@ public RunAsPermission runAs() { @Override public boolean hasFieldOrDocumentLevelSecurity() { - return super.hasFieldOrDocumentLevelSecurity() || limitedBy.hasFieldOrDocumentLevelSecurity(); + return baseRole.hasFieldOrDocumentLevelSecurity() || limitedByRole.hasFieldOrDocumentLevelSecurity(); } @Override @@ -75,16 +81,13 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - if (super.equals(o) == false) { - return false; - } LimitedRole that = (LimitedRole) o; - return this.limitedBy.equals(that.limitedBy); + return baseRole.equals(that.baseRole) && this.limitedByRole.equals(that.limitedByRole); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), limitedBy); + return Objects.hash(baseRole, limitedByRole); } @Override @@ -94,13 +97,13 @@ public IndicesAccessControl authorize( Map aliasAndIndexLookup, FieldPermissionsCache fieldPermissionsCache ) { - IndicesAccessControl indicesAccessControl = super.authorize( + IndicesAccessControl indicesAccessControl = baseRole.authorize( action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache ); - IndicesAccessControl limitedByIndicesAccessControl = limitedBy.authorize( + IndicesAccessControl limitedByIndicesAccessControl = limitedByRole.authorize( action, requestedIndicesOrAliases, aliasAndIndexLookup, @@ -115,15 +118,15 @@ public IndicesAccessControl authorize( */ @Override public Predicate allowedIndicesMatcher(String action) { - Predicate predicate = super.indices().allowedIndicesMatcher(action); - predicate = predicate.and(limitedBy.indices().allowedIndicesMatcher(action)); + Predicate predicate = baseRole.indices().allowedIndicesMatcher(action); + predicate = predicate.and(limitedByRole.indices().allowedIndicesMatcher(action)); return predicate; } @Override public Automaton allowedActionsMatcher(String index) { - final Automaton allowedMatcher = super.allowedActionsMatcher(index); - final Automaton limitedByMatcher = limitedBy.allowedActionsMatcher(index); + final Automaton allowedMatcher = baseRole.allowedActionsMatcher(index); + final Automaton limitedByMatcher = limitedByRole.allowedActionsMatcher(index); return Automatons.intersectAndMinimize(allowedMatcher, limitedByMatcher); } @@ -135,7 +138,7 @@ public Automaton allowedActionsMatcher(String index) { */ @Override public boolean checkIndicesAction(String action) { - return super.checkIndicesAction(action) && limitedBy.checkIndicesAction(action); + return baseRole.checkIndicesAction(action) && limitedByRole.checkIndicesAction(action); } /** @@ -155,12 +158,9 @@ public ResourcePrivilegesMap checkIndicesPrivileges( boolean allowRestrictedIndices, Set checkForPrivileges ) { - ResourcePrivilegesMap resourcePrivilegesMap = super.indices().checkResourcePrivileges( - checkForIndexPatterns, - allowRestrictedIndices, - checkForPrivileges - ); - ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.indices() + ResourcePrivilegesMap resourcePrivilegesMap = baseRole.indices() + .checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges); + ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.indices() .checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges); return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole); } @@ -177,7 +177,8 @@ public ResourcePrivilegesMap checkIndicesPrivileges( */ @Override public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { - return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication); + return baseRole.checkClusterAction(action, request, authentication) + && limitedByRole.checkClusterAction(action, request, authentication); } /** @@ -189,7 +190,7 @@ public boolean checkClusterAction(String action, TransportRequest request, Authe */ @Override public boolean grants(ClusterPrivilege clusterPrivilege) { - return super.grants(clusterPrivilege) && limitedBy.grants(clusterPrivilege); + return baseRole.grants(clusterPrivilege) && limitedByRole.grants(clusterPrivilege); } /** @@ -212,31 +213,15 @@ public ResourcePrivilegesMap checkApplicationResourcePrivileges( Set checkForPrivilegeNames, Collection storedPrivileges ) { - ResourcePrivilegesMap resourcePrivilegesMap = super.application().checkResourcePrivileges( - applicationName, - checkForResources, - checkForPrivilegeNames, - storedPrivileges - ); - ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.application() + ResourcePrivilegesMap resourcePrivilegesMap = baseRole.application() + .checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges); + ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.application() .checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges); return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole); } @Override public boolean checkRunAs(String runAs) { - return super.checkRunAs(runAs) && limitedBy.checkRunAs(runAs); - } - - /** - * Create a new role defined by given role and the limited role. - * - * @param fromRole existing role {@link Role} - * @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role} - * @return {@link LimitedRole} - */ - public static LimitedRole createLimitedRole(Role fromRole, Role limitedByRole) { - Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role"); - return new LimitedRole(fromRole.cluster(), fromRole.indices(), fromRole.application(), fromRole.runAs(), limitedByRole); + return baseRole.checkRunAs(runAs) && limitedByRole.checkRunAs(runAs); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java index 9fdb2c91f01d6..3967fae14e755 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + package org.elasticsearch.xpack.core.security.authz.permission; import org.apache.lucene.util.automaton.Automaton; @@ -31,82 +32,46 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Predicate; -public class Role { - - public static final Role EMPTY = Role.builder(Automatons.EMPTY, "__empty").build(); - - private final String[] names; - private final ClusterPermission cluster; - private final IndicesPermission indices; - private final ApplicationPermission application; - private final RunAsPermission runAs; +public interface Role { - Role(String[] names, ClusterPermission cluster, IndicesPermission indices, ApplicationPermission application, RunAsPermission runAs) { - this.names = names; - this.cluster = Objects.requireNonNull(cluster); - this.indices = Objects.requireNonNull(indices); - this.application = Objects.requireNonNull(application); - this.runAs = Objects.requireNonNull(runAs); - } - - public String[] names() { - return names; - } + Role EMPTY = builder(Automatons.EMPTY, "__empty").build(); - public ClusterPermission cluster() { - return cluster; - } + String[] names(); - public IndicesPermission indices() { - return indices; - } + ClusterPermission cluster(); - public ApplicationPermission application() { - return application; - } + IndicesPermission indices(); - public RunAsPermission runAs() { - return runAs; - } + ApplicationPermission application(); - public boolean hasFieldOrDocumentLevelSecurity() { - return indices.hasFieldOrDocumentLevelSecurity(); - } + RunAsPermission runAs(); /** - * @param restrictedIndices An automaton that can determine whether a string names - * a restricted index. For simple unit tests, this can be - * {@link Automatons#EMPTY}. - * @param names Names of roles. - * @return A builder for a role + * Whether the Role has any field or document level security enabled index privileges + * @return */ - public static Builder builder(Automaton restrictedIndices, String... names) { - return new Builder(restrictedIndices, names); - } - - public static Builder builder(RoleDescriptor rd, FieldPermissionsCache fieldPermissionsCache, Automaton restrictedIndices) { - return new Builder(rd, fieldPermissionsCache, restrictedIndices); - } + boolean hasFieldOrDocumentLevelSecurity(); /** * @return A predicate that will match all the indices that this role * has the privilege for executing the given action on. */ - public Predicate allowedIndicesMatcher(String action) { - return indices.allowedIndicesMatcher(action); - } + Predicate allowedIndicesMatcher(String action); - public Automaton allowedActionsMatcher(String index) { - return indices.allowedActionsMatcher(index); - } + /** + * Returns an {@link Automaton} that matches all action names allowed for the given index + */ + Automaton allowedActionsMatcher(String index); - public boolean checkRunAs(String runAsName) { - return runAs.check(runAsName); - } + /** + * Check if the role is allowed to run-as the given username. + * @param runAsName + * @return + */ + boolean checkRunAs(String runAsName); /** * Check if indices permissions allow for the given action @@ -114,9 +79,7 @@ public boolean checkRunAs(String runAsName) { * @param action indices action * @return {@code true} if action is allowed else returns {@code false} */ - public boolean checkIndicesAction(String action) { - return indices.check(action); - } + boolean checkIndicesAction(String action); /** * For given index patterns and index privileges determines allowed privileges and creates an instance of {@link ResourcePrivilegesMap} @@ -128,13 +91,11 @@ public boolean checkIndicesAction(String action) { * @param checkForPrivileges check permission grants for the set of index privileges * @return an instance of {@link ResourcePrivilegesMap} */ - public ResourcePrivilegesMap checkIndicesPrivileges( + ResourcePrivilegesMap checkIndicesPrivileges( Set checkForIndexPatterns, boolean allowRestrictedIndices, Set checkForPrivileges - ) { - return indices.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges); - } + ); /** * Check if cluster permissions allow for the given action in the context of given @@ -145,9 +106,7 @@ public ResourcePrivilegesMap checkIndicesPrivileges( * @param authentication {@link Authentication} * @return {@code true} if action is allowed else returns {@code false} */ - public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { - return cluster.check(action, request, authentication); - } + boolean checkClusterAction(String action, TransportRequest request, Authentication authentication); /** * Check if cluster permissions grants the given cluster privilege @@ -155,9 +114,7 @@ public boolean checkClusterAction(String action, TransportRequest request, Authe * @param clusterPrivilege cluster privilege * @return {@code true} if cluster privilege is allowed else returns {@code false} */ - public boolean grants(ClusterPrivilege clusterPrivilege) { - return cluster.implies(clusterPrivilege.buildPermission(ClusterPermission.builder()).build()); - } + boolean grants(ClusterPrivilege clusterPrivilege); /** * For a given application, checks for the privileges for resources and returns an instance of {@link ResourcePrivilegesMap} holding a @@ -171,53 +128,48 @@ public boolean grants(ClusterPrivilege clusterPrivilege) { * performed * @return an instance of {@link ResourcePrivilegesMap} */ - public ResourcePrivilegesMap checkApplicationResourcePrivileges( - final String applicationName, + ResourcePrivilegesMap checkApplicationResourcePrivileges( + String applicationName, Set checkForResources, Set checkForPrivilegeNames, Collection storedPrivileges - ) { - return application.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges); - } + ); /** * Returns whether at least one group encapsulated by this indices permissions is authorized to execute the * specified action with the requested indices/aliases. At the same time if field and/or document level security * is configured for any group also the allowed fields and role queries are resolved. */ - public IndicesAccessControl authorize( + IndicesAccessControl authorize( String action, Set requestedIndicesOrAliases, Map aliasAndIndexLookup, FieldPermissionsCache fieldPermissionsCache - ) { - return indices.authorize(action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache); + ); + + /*** + * Creates a {@link LimitedRole} that uses this Role as base and the given role as limited-by. + */ + default LimitedRole limitedBy(Role role) { + return new LimitedRole(this, role); } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Role that = (Role) o; - return Arrays.equals(this.names, that.names) - && this.cluster.equals(that.cluster) - && this.indices.equals(that.indices) - && this.application.equals(that.application) - && this.runAs.equals(that.runAs); + /** + * @param restrictedIndices An automaton that can determine whether a string names + * a restricted index. For simple unit tests, this can be + * {@link Automatons#EMPTY}. + * @param names Names of roles. + * @return A builder for a role + */ + static Builder builder(Automaton restrictedIndices, String... names) { + return new Builder(restrictedIndices, names); } - @Override - public int hashCode() { - int result = Objects.hash(cluster, indices, application, runAs); - result = 31 * result + Arrays.hashCode(names); - return result; + static Builder builder(RoleDescriptor rd, FieldPermissionsCache fieldPermissionsCache, Automaton restrictedIndices) { + return new Builder(rd, fieldPermissionsCache, restrictedIndices); } - public static class Builder { + class Builder { private final String[] names; private ClusterPermission cluster = ClusterPermission.NONE; @@ -288,7 +240,7 @@ public Builder addApplicationPrivilege(ApplicationPrivilege privilege, Set convertFromIndicesPrivileges( @@ -368,5 +320,4 @@ private IndicesPermissionGroupDefinition( } } } - } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java new file mode 100644 index 0000000000000..081e76eeab6a2 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/SimpleRole.java @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core.security.authz.permission; + +import org.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.cluster.metadata.IndexAbstraction; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; +import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; +import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; + +public class SimpleRole implements Role { + + private final String[] names; + private final ClusterPermission cluster; + private final IndicesPermission indices; + private final ApplicationPermission application; + private final RunAsPermission runAs; + + SimpleRole( + String[] names, + ClusterPermission cluster, + IndicesPermission indices, + ApplicationPermission application, + RunAsPermission runAs + ) { + this.names = names; + this.cluster = Objects.requireNonNull(cluster); + this.indices = Objects.requireNonNull(indices); + this.application = Objects.requireNonNull(application); + this.runAs = Objects.requireNonNull(runAs); + } + + @Override + public String[] names() { + return names; + } + + @Override + public ClusterPermission cluster() { + return cluster; + } + + @Override + public IndicesPermission indices() { + return indices; + } + + @Override + public ApplicationPermission application() { + return application; + } + + @Override + public RunAsPermission runAs() { + return runAs; + } + + @Override + public boolean hasFieldOrDocumentLevelSecurity() { + return indices.hasFieldOrDocumentLevelSecurity(); + } + + @Override + public Predicate allowedIndicesMatcher(String action) { + return indices.allowedIndicesMatcher(action); + } + + @Override + public Automaton allowedActionsMatcher(String index) { + return indices.allowedActionsMatcher(index); + } + + @Override + public boolean checkRunAs(String runAsName) { + return runAs.check(runAsName); + } + + @Override + public boolean checkIndicesAction(String action) { + return indices.check(action); + } + + @Override + public ResourcePrivilegesMap checkIndicesPrivileges( + Set checkForIndexPatterns, + boolean allowRestrictedIndices, + Set checkForPrivileges + ) { + return indices.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges); + } + + @Override + public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { + return cluster.check(action, request, authentication); + } + + @Override + public boolean grants(ClusterPrivilege clusterPrivilege) { + return cluster.implies(clusterPrivilege.buildPermission(ClusterPermission.builder()).build()); + } + + @Override + public ResourcePrivilegesMap checkApplicationResourcePrivileges( + final String applicationName, + Set checkForResources, + Set checkForPrivilegeNames, + Collection storedPrivileges + ) { + return application.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges); + } + + @Override + public IndicesAccessControl authorize( + String action, + Set requestedIndicesOrAliases, + Map aliasAndIndexLookup, + FieldPermissionsCache fieldPermissionsCache + ) { + return indices.authorize(action, requestedIndicesOrAliases, aliasAndIndexLookup, fieldPermissionsCache); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SimpleRole that = (SimpleRole) o; + return Arrays.equals(this.names, that.names) + && this.cluster.equals(that.cluster) + && this.indices.equals(that.indices) + && this.application.equals(that.application) + && this.runAs.equals(that.runAs); + } + + @Override + public int hashCode() { + int result = Objects.hash(cluster, indices, application, runAs); + result = 31 * result + Arrays.hashCode(names); + return result; + } + +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersection.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersection.java new file mode 100644 index 0000000000000..7c3f1df38c187 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersection.java @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.security.authz.store; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.GroupedActionListener; +import org.elasticsearch.xpack.core.security.authz.permission.Role; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; + +/** + * This class wraps a list of role references that should be intersected when building the runtime Role. + */ +public class RoleReferenceIntersection { + + private final List roleReferences; + + public RoleReferenceIntersection(RoleReference... roleReferences) { + this(List.of(roleReferences)); + } + + public RoleReferenceIntersection(List roleReferences) { + assert roleReferences != null && false == roleReferences.isEmpty() : "role references cannot be null or empty"; + this.roleReferences = Objects.requireNonNull(roleReferences); + } + + public List getRoleReferences() { + return roleReferences; + } + + public void buildRole(BiConsumer> singleRoleBuilder, ActionListener roleActionListener) { + final GroupedActionListener roleGroupedActionListener = new GroupedActionListener<>(ActionListener.wrap(roles -> { + assert false == roles.isEmpty(); + final Iterator iterator = roles.stream().iterator(); + Role finalRole = iterator.next(); + while (iterator.hasNext()) { + finalRole = finalRole.limitedBy(iterator.next()); + } + roleActionListener.onResponse(finalRole); + }, roleActionListener::onFailure), roleReferences.size()); + + roleReferences.forEach(roleReference -> singleRoleBuilder.accept(roleReference, roleGroupedActionListener)); + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java index 2b0f5dc351a50..3ebc0b2a22e6c 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java @@ -21,6 +21,7 @@ import org.elasticsearch.xpack.core.security.authz.store.RoleReference.BwcApiKeyRoleReference; import org.elasticsearch.xpack.core.security.authz.store.RoleReference.NamedRoleReference; import org.elasticsearch.xpack.core.security.authz.store.RoleReference.ServiceAccountRoleReference; +import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.User; @@ -55,7 +56,8 @@ public void testGetRoleReferencesForRegularUser() { final AnonymousUser anonymousUser = randomFrom(getAnonymousUser(), null); - final List roleReferences = subject.getRoleReferences(anonymousUser); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(anonymousUser); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); assertThat(roleReferences, hasSize(1)); assertThat(roleReferences.get(0), instanceOf(NamedRoleReference.class)); final NamedRoleReference namedRoleReference = (NamedRoleReference) roleReferences.get(0); @@ -75,7 +77,8 @@ public void testGetRoleReferencesForAnonymousUser() { Map.of() ); - final List roleReferences = subject.getRoleReferences(anonymousUser); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(anonymousUser); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); assertThat(roleReferences, hasSize(1)); assertThat(roleReferences.get(0), instanceOf(NamedRoleReference.class)); final NamedRoleReference namedRoleReference = (NamedRoleReference) roleReferences.get(0); @@ -92,7 +95,8 @@ public void testGetRoleReferencesForServiceAccount() { Map.of() ); - final List roleReferences = subject.getRoleReferences(getAnonymousUser()); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(getAnonymousUser()); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); assertThat(roleReferences, hasSize(1)); assertThat(roleReferences.get(0), instanceOf(ServiceAccountRoleReference.class)); final ServiceAccountRoleReference serviceAccountRoleReference = (ServiceAccountRoleReference) roleReferences.get(0); @@ -122,7 +126,8 @@ public void testGetRoleReferencesForApiKey() { authMetadata ); - final List roleReferences = subject.getRoleReferences(getAnonymousUser()); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(getAnonymousUser()); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); if (emptyRoleBytes) { assertThat(roleReferences, contains(isA(ApiKeyRoleReference.class))); final ApiKeyRoleReference roleReference = (ApiKeyRoleReference) roleReferences.get(0); @@ -164,7 +169,8 @@ public void testGetRoleReferencesForApiKeyBwc() { authMetadata ); - final List roleReferences = subject.getRoleReferences(getAnonymousUser()); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(getAnonymousUser()); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); if (emptyApiKeyRoleDescriptor) { assertThat(roleReferences, contains(isA(BwcApiKeyRoleReference.class))); @@ -204,7 +210,8 @@ public void testGetFleetApiKeyRoleReferenceBwcBugFix() { ) ); - final List roleReferences = subject.getRoleReferences(getAnonymousUser()); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(getAnonymousUser()); + final List roleReferences = roleReferenceIntersection.getRoleReferences(); assertThat(roleReferences, contains(isA(ApiKeyRoleReference.class), isA(ApiKeyRoleReference.class))); final ApiKeyRoleReference limitedByRoleReference = (ApiKeyRoleReference) roleReferences.get(1); assertThat(limitedByRoleReference.getRoleDescriptorsBytes(), equalTo(FLEET_SERVER_ROLE_DESCRIPTOR_BYTES_V_7_14)); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java index 0a97fe68d12d8..c5ce8059e2481 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java @@ -55,11 +55,11 @@ public void setup() { public void testRoleConstructorWithLimitedRole() { Role fromRole = Role.builder(Automatons.EMPTY, "a-role").build(); Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").build(); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertNotNull(role); assertThat(role.names(), is(limitedByRole.names())); - NullPointerException npe = expectThrows(NullPointerException.class, () -> LimitedRole.createLimitedRole(fromRole, null)); + NullPointerException npe = expectThrows(NullPointerException.class, () -> fromRole.limitedBy(null)); assertThat(npe.getMessage(), containsString("limited by role is required to create limited role")); } @@ -146,7 +146,7 @@ public void testAuthorize() { assertThat(iac.getIndexPermissions("_index1"), is(notNullValue())); assertThat(iac.getIndexPermissions("_index1").isGranted(), is(false)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); iac = role.authorize(SearchAction.NAME, Sets.newHashSet("_index", "_alias1"), md.getIndicesLookup(), fieldPermissionsCache); assertThat(iac.getIndexPermissions("_index"), is(notNullValue())); assertThat(iac.getIndexPermissions("_index").isGranted(), is(true)); @@ -190,7 +190,7 @@ public void testCheckClusterAction() { is(true) ); assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true)); assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(false)); } @@ -199,7 +199,7 @@ public void testCheckClusterAction() { .cluster(Collections.singleton("monitor"), Collections.emptyList()) .build(); assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(false)); assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(false)); } @@ -214,14 +214,14 @@ public void testCheckIndicesAction() { Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.ALL, "ind-1").build(); assertThat(limitedByRole.checkIndicesAction(SearchAction.NAME), is(true)); assertThat(limitedByRole.checkIndicesAction(CreateIndexAction.NAME), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.checkIndicesAction(SearchAction.NAME), is(true)); assertThat(role.checkIndicesAction(CreateIndexAction.NAME), is(false)); } { Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.NONE, "ind-1").build(); assertThat(limitedByRole.checkIndicesAction(SearchAction.NAME), is(false)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.checkIndicesAction(SearchAction.NAME), is(false)); assertThat(role.checkIndicesAction(CreateIndexAction.NAME), is(false)); } @@ -238,7 +238,7 @@ public void testAllowedIndicesMatcher() { assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true)); assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-11")), is(false)); assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true)); assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-11")), is(false)); assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(false)); @@ -247,7 +247,7 @@ public void testAllowedIndicesMatcher() { Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.READ, "ind-*").build(); assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true)); assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true)); assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(false)); } @@ -269,7 +269,7 @@ public void testAllowedActionsMatcher() { Predicate limitedByRolePredicated = Automatons.predicate(limitedByRoleAutomaton); assertThat(limitedByRolePredicated.test(SearchAction.NAME), is(true)); assertThat(limitedByRolePredicated.test(BulkAction.NAME), is(false)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); Automaton roleAutomaton = role.allowedActionsMatcher("index1"); Predicate rolePredicate = Automatons.predicate(roleAutomaton); @@ -300,7 +300,7 @@ public void testCheckClusterPrivilege() { .build(); assertThat(limitedByRole.grants(ClusterPrivilegeResolver.ALL), is(true)); assertThat(limitedByRole.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.grants(ClusterPrivilegeResolver.ALL), is(false)); assertThat(role.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(true)); } @@ -310,7 +310,7 @@ public void testCheckClusterPrivilege() { .build(); assertThat(limitedByRole.grants(ClusterPrivilegeResolver.ALL), is(false)); assertThat(limitedByRole.grants(ClusterPrivilegeResolver.MONITOR), is(true)); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); assertThat(role.grants(ClusterPrivilegeResolver.ALL), is(false)); assertThat(role.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(false)); assertThat(role.grants(ClusterPrivilegeResolver.MONITOR), is(false)); @@ -370,7 +370,7 @@ public void testGetPrivilegesForIndexPatterns() { ); verifyResourcesPrivileges(resourcePrivileges, expectedAppPrivsByResource); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); resourcePrivileges = role.checkIndicesPrivileges(Collections.singleton("ind-1"), true, Collections.singleton("read")); expectedAppPrivsByResource = new ResourcePrivilegesMap( true, @@ -422,7 +422,7 @@ public void testGetPrivilegesForIndexPatterns() { ); verifyResourcesPrivileges(resourcePrivileges, expectedAppPrivsByResource); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); resourcePrivileges = role.checkIndicesPrivileges( Sets.newHashSet("ind-1", "ind-2", ".security"), true, @@ -608,7 +608,7 @@ public void testGetApplicationPrivilegesByResource() { ); verifyResourcesPrivileges(appPrivsByResource, expectedAppPrivsByResource); - Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); + Role role = fromRole.limitedBy(limitedByRole); appPrivsByResource = role.checkApplicationResourcePrivileges( "app2", Collections.singleton("foo/bar/a"), diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersectionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersectionTests.java new file mode 100644 index 0000000000000..6bab5529323e6 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/RoleReferenceIntersectionTests.java @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.security.authz.store; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authz.permission.LimitedRole; +import org.elasticsearch.xpack.core.security.authz.permission.Role; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class RoleReferenceIntersectionTests extends ESTestCase { + + public void testBuildRoleForSingleRoleReference() { + final RoleReference roleReference = mock(RoleReference.class); + final Role role = mock(Role.class); + final RoleReferenceIntersection roleReferenceIntersection = new RoleReferenceIntersection(roleReference); + final BiConsumer> singleRoleBuilder = (r, l) -> { + assertThat(r, is(roleReference)); + l.onResponse(role); + }; + + final PlainActionFuture future = new PlainActionFuture<>(); + roleReferenceIntersection.buildRole(singleRoleBuilder, future); + assertThat(future.actionGet(), is(role)); + } + + public void testBuildRoleForListOfRoleReferences() { + final int size = randomIntBetween(2, 3); + final List roleReferences = new ArrayList<>(size); + final List roles = new ArrayList<>(size); + + for (int i = 0; i < size; i++) { + final RoleReference roleReference = mock(RoleReference.class); + when(roleReference.id()).thenReturn(new RoleKey(Set.of(), String.valueOf(i))); + roleReferences.add(roleReference); + + final Role role = mock(Role.class); + when(role.limitedBy(any())).thenCallRealMethod(); + roles.add(role); + } + + final RoleReferenceIntersection roleReferenceIntersection = new RoleReferenceIntersection(roleReferences); + final BiConsumer> singleRoleBuilder = (rf, l) -> { + l.onResponse(roles.get(Integer.parseInt(rf.id().getSource()))); + }; + + final PlainActionFuture future = new PlainActionFuture<>(); + roleReferenceIntersection.buildRole(singleRoleBuilder, future); + + final Role role = future.actionGet(); + assertThat(role, instanceOf(LimitedRole.class)); + + verify(roles.get(0)).limitedBy(roles.get(1)); + + if (size == 2) { + assertThat(role, equalTo(new LimitedRole(roles.get(0), roles.get(1)))); + } else { + assertThat(role, equalTo(new LimitedRole(new LimitedRole(roles.get(0), roles.get(1)), roles.get(2)))); + } + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index b9e0f318451af..eff9c70594325 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -34,7 +34,6 @@ import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup; -import org.elasticsearch.xpack.core.security.authz.permission.LimitedRole; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege; @@ -43,6 +42,7 @@ import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleKey; import org.elasticsearch.xpack.core.security.authz.store.RoleReference; +import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection; import org.elasticsearch.xpack.core.security.authz.store.RolesRetrievalResult; import org.elasticsearch.xpack.core.security.support.CacheIteratorHelper; import org.elasticsearch.xpack.core.security.user.AnonymousUser; @@ -193,25 +193,8 @@ public void getRole(Subject subject, ActionListener roleActionListener) { assert false == User.isInternal(subject.getUser()) : "Internal user should not pass here"; - final List roleReferences = subject.getRoleReferences(anonymousUser); - // TODO: Two levels of nesting can be relaxed in future - assert roleReferences.size() <= 2 : "only support up to one level of limiting"; - assert false == roleReferences.isEmpty() : "role references cannot be empty"; - - buildRoleFromRoleReference(roleReferences.get(0), ActionListener.wrap(role -> { - if (roleReferences.size() == 1) { - roleActionListener.onResponse(role); - } else { - buildRoleFromRoleReference( - roleReferences.get(1), - ActionListener.wrap( - limitedByRole -> roleActionListener.onResponse(LimitedRole.createLimitedRole(role, limitedByRole)), - roleActionListener::onFailure - ) - ); - } - - }, roleActionListener::onFailure)); + final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(anonymousUser); + roleReferenceIntersection.buildRole(this::buildRoleFromRoleReference, roleActionListener); } // Accessible by tests diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 5df9c4b8c735f..1e5f711bada76 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -70,6 +70,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleReference; +import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; import org.elasticsearch.xpack.core.security.index.IndexAuditTrailField; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; @@ -1894,7 +1895,9 @@ public void testXpackUserHasClusterPrivileges() { private void getRoleForRoleNames(CompositeRolesStore rolesStore, Collection roleNames, ActionListener listener) { final Subject subject = mock(Subject.class); - when(subject.getRoleReferences(any())).thenReturn(List.of(new RoleReference.NamedRoleReference(roleNames.toArray(String[]::new)))); + when(subject.getRoleReferenceIntersection(any())).thenReturn( + new RoleReferenceIntersection(new RoleReference.NamedRoleReference(roleNames.toArray(String[]::new))) + ); rolesStore.getRole(subject, listener); }