Skip to content

Commit c491279

Browse files
authored
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
1 parent c98833f commit c491279

File tree

10 files changed

+427
-213
lines changed

10 files changed

+427
-213
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Subject.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
import org.elasticsearch.core.Nullable;
1616
import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSettings;
1717
import org.elasticsearch.xpack.core.security.authz.store.RoleReference;
18+
import org.elasticsearch.xpack.core.security.authz.store.RoleReferenceIntersection;
1819
import org.elasticsearch.xpack.core.security.user.AnonymousUser;
1920
import org.elasticsearch.xpack.core.security.user.User;
2021

21-
import java.util.List;
2222
import java.util.Map;
2323

2424
import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES;
@@ -85,27 +85,23 @@ public Map<String, Object> getMetadata() {
8585
return metadata;
8686
}
8787

88-
/**
89-
* Return a List of RoleReferences that represents role definitions associated to the subject.
90-
* The final role of this subject should be the intersection of all role references in the list.
91-
*/
92-
public List<RoleReference> getRoleReferences(@Nullable AnonymousUser anonymousUser) {
88+
public RoleReferenceIntersection getRoleReferenceIntersection(@Nullable AnonymousUser anonymousUser) {
9389
switch (type) {
9490
case USER:
9591
return buildRoleReferencesForUser(anonymousUser);
9692
case API_KEY:
9793
return buildRoleReferencesForApiKey();
9894
case SERVICE_ACCOUNT:
99-
return List.of(new RoleReference.ServiceAccountRoleReference(user.principal()));
95+
return new RoleReferenceIntersection(new RoleReference.ServiceAccountRoleReference(user.principal()));
10096
default:
10197
assert false : "unknown subject type: [" + type + "]";
10298
throw new IllegalStateException("unknown subject type: [" + type + "]");
10399
}
104100
}
105101

106-
private List<RoleReference> buildRoleReferencesForUser(AnonymousUser anonymousUser) {
102+
private RoleReferenceIntersection buildRoleReferencesForUser(AnonymousUser anonymousUser) {
107103
if (user.equals(anonymousUser)) {
108-
return List.of(new RoleReference.NamedRoleReference(user.roles()));
104+
return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(user.roles()));
109105
}
110106
final String[] allRoleNames;
111107
if (anonymousUser == null || false == anonymousUser.enabled()) {
@@ -117,10 +113,10 @@ private List<RoleReference> buildRoleReferencesForUser(AnonymousUser anonymousUs
117113
}
118114
allRoleNames = ArrayUtils.concat(user.roles(), anonymousUser.roles());
119115
}
120-
return List.of(new RoleReference.NamedRoleReference(allRoleNames));
116+
return new RoleReferenceIntersection(new RoleReference.NamedRoleReference(allRoleNames));
121117
}
122118

123-
private List<RoleReference> buildRoleReferencesForApiKey() {
119+
private RoleReferenceIntersection buildRoleReferencesForApiKey() {
124120
if (version.before(VERSION_API_KEY_ROLES_AS_BYTES)) {
125121
return buildRolesReferenceForApiKeyBwc();
126122
}
@@ -136,9 +132,9 @@ private List<RoleReference> buildRoleReferencesForApiKey() {
136132
RoleReference.ApiKeyRoleType.LIMITED_BY
137133
);
138134
if (isEmptyRoleDescriptorsBytes(roleDescriptorsBytes)) {
139-
return List.of(limitedByRoleReference);
135+
return new RoleReferenceIntersection(limitedByRoleReference);
140136
}
141-
return List.of(
137+
return new RoleReferenceIntersection(
142138
new RoleReference.ApiKeyRoleReference(apiKeyId, roleDescriptorsBytes, RoleReference.ApiKeyRoleType.ASSIGNED),
143139
limitedByRoleReference
144140
);
@@ -148,7 +144,7 @@ private boolean isEmptyRoleDescriptorsBytes(BytesReference roleDescriptorsBytes)
148144
return roleDescriptorsBytes == null || (roleDescriptorsBytes.length() == 2 && "{}".equals(roleDescriptorsBytes.utf8ToString()));
149145
}
150146

151-
private List<RoleReference> buildRolesReferenceForApiKeyBwc() {
147+
private RoleReferenceIntersection buildRolesReferenceForApiKeyBwc() {
152148
final String apiKeyId = (String) metadata.get(AuthenticationField.API_KEY_ID_KEY);
153149
final Map<String, Object> roleDescriptorsMap = getRoleDescriptorMap(API_KEY_ROLE_DESCRIPTORS_KEY);
154150
final Map<String, Object> limitedByRoleDescriptorsMap = getRoleDescriptorMap(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY);
@@ -161,9 +157,9 @@ private List<RoleReference> buildRolesReferenceForApiKeyBwc() {
161157
RoleReference.ApiKeyRoleType.LIMITED_BY
162158
);
163159
if (roleDescriptorsMap == null || roleDescriptorsMap.isEmpty()) {
164-
return List.of(limitedByRoleReference);
160+
return new RoleReferenceIntersection(limitedByRoleReference);
165161
} else {
166-
return List.of(
162+
return new RoleReferenceIntersection(
167163
new RoleReference.BwcApiKeyRoleReference(apiKeyId, roleDescriptorsMap, RoleReference.ApiKeyRoleType.ASSIGNED),
168164
limitedByRoleReference
169165
);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,30 @@
2222
import java.util.Set;
2323
import java.util.function.Predicate;
2424

25-
// TODO: extract a Role interface so limitedRole can be more than 2 levels
2625
/**
2726
* A {@link Role} limited by another role.<br>
2827
* The effective permissions returned on {@link #authorize(String, Set, Map, FieldPermissionsCache)} call would be limited by the
2928
* provided role.
3029
*/
31-
public final class LimitedRole extends Role {
32-
private final Role limitedBy;
33-
34-
LimitedRole(
35-
ClusterPermission cluster,
36-
IndicesPermission indices,
37-
ApplicationPermission application,
38-
RunAsPermission runAs,
39-
Role limitedBy
40-
) {
41-
super(Objects.requireNonNull(limitedBy, "limiting role is required").names(), cluster, indices, application, runAs);
42-
this.limitedBy = limitedBy;
30+
public final class LimitedRole implements Role {
31+
private final Role baseRole;
32+
private final Role limitedByRole;
33+
34+
/**
35+
* Create a new role defined by given role and the limited role.
36+
*
37+
* @param baseRole existing role {@link Role}
38+
* @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role}
39+
*/
40+
public LimitedRole(Role baseRole, Role limitedByRole) {
41+
this.baseRole = Objects.requireNonNull(baseRole);
42+
this.limitedByRole = Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role");
43+
}
44+
45+
@Override
46+
public String[] names() {
47+
// TODO: this is to retain existing behaviour, but it is not accurate
48+
return limitedByRole.names();
4349
}
4450

4551
@Override
@@ -64,7 +70,7 @@ public RunAsPermission runAs() {
6470

6571
@Override
6672
public boolean hasFieldOrDocumentLevelSecurity() {
67-
return super.hasFieldOrDocumentLevelSecurity() || limitedBy.hasFieldOrDocumentLevelSecurity();
73+
return baseRole.hasFieldOrDocumentLevelSecurity() || limitedByRole.hasFieldOrDocumentLevelSecurity();
6874
}
6975

7076
@Override
@@ -75,16 +81,13 @@ public boolean equals(Object o) {
7581
if (o == null || getClass() != o.getClass()) {
7682
return false;
7783
}
78-
if (super.equals(o) == false) {
79-
return false;
80-
}
8184
LimitedRole that = (LimitedRole) o;
82-
return this.limitedBy.equals(that.limitedBy);
85+
return baseRole.equals(that.baseRole) && this.limitedByRole.equals(that.limitedByRole);
8386
}
8487

8588
@Override
8689
public int hashCode() {
87-
return Objects.hash(super.hashCode(), limitedBy);
90+
return Objects.hash(baseRole, limitedByRole);
8891
}
8992

9093
@Override
@@ -94,13 +97,13 @@ public IndicesAccessControl authorize(
9497
Map<String, IndexAbstraction> aliasAndIndexLookup,
9598
FieldPermissionsCache fieldPermissionsCache
9699
) {
97-
IndicesAccessControl indicesAccessControl = super.authorize(
100+
IndicesAccessControl indicesAccessControl = baseRole.authorize(
98101
action,
99102
requestedIndicesOrAliases,
100103
aliasAndIndexLookup,
101104
fieldPermissionsCache
102105
);
103-
IndicesAccessControl limitedByIndicesAccessControl = limitedBy.authorize(
106+
IndicesAccessControl limitedByIndicesAccessControl = limitedByRole.authorize(
104107
action,
105108
requestedIndicesOrAliases,
106109
aliasAndIndexLookup,
@@ -115,15 +118,15 @@ public IndicesAccessControl authorize(
115118
*/
116119
@Override
117120
public Predicate<IndexAbstraction> allowedIndicesMatcher(String action) {
118-
Predicate<IndexAbstraction> predicate = super.indices().allowedIndicesMatcher(action);
119-
predicate = predicate.and(limitedBy.indices().allowedIndicesMatcher(action));
121+
Predicate<IndexAbstraction> predicate = baseRole.indices().allowedIndicesMatcher(action);
122+
predicate = predicate.and(limitedByRole.indices().allowedIndicesMatcher(action));
120123
return predicate;
121124
}
122125

123126
@Override
124127
public Automaton allowedActionsMatcher(String index) {
125-
final Automaton allowedMatcher = super.allowedActionsMatcher(index);
126-
final Automaton limitedByMatcher = limitedBy.allowedActionsMatcher(index);
128+
final Automaton allowedMatcher = baseRole.allowedActionsMatcher(index);
129+
final Automaton limitedByMatcher = limitedByRole.allowedActionsMatcher(index);
127130
return Automatons.intersectAndMinimize(allowedMatcher, limitedByMatcher);
128131
}
129132

@@ -135,7 +138,7 @@ public Automaton allowedActionsMatcher(String index) {
135138
*/
136139
@Override
137140
public boolean checkIndicesAction(String action) {
138-
return super.checkIndicesAction(action) && limitedBy.checkIndicesAction(action);
141+
return baseRole.checkIndicesAction(action) && limitedByRole.checkIndicesAction(action);
139142
}
140143

141144
/**
@@ -155,12 +158,9 @@ public ResourcePrivilegesMap checkIndicesPrivileges(
155158
boolean allowRestrictedIndices,
156159
Set<String> checkForPrivileges
157160
) {
158-
ResourcePrivilegesMap resourcePrivilegesMap = super.indices().checkResourcePrivileges(
159-
checkForIndexPatterns,
160-
allowRestrictedIndices,
161-
checkForPrivileges
162-
);
163-
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.indices()
161+
ResourcePrivilegesMap resourcePrivilegesMap = baseRole.indices()
162+
.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges);
163+
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.indices()
164164
.checkResourcePrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges);
165165
return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole);
166166
}
@@ -177,7 +177,8 @@ public ResourcePrivilegesMap checkIndicesPrivileges(
177177
*/
178178
@Override
179179
public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) {
180-
return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication);
180+
return baseRole.checkClusterAction(action, request, authentication)
181+
&& limitedByRole.checkClusterAction(action, request, authentication);
181182
}
182183

183184
/**
@@ -189,7 +190,7 @@ public boolean checkClusterAction(String action, TransportRequest request, Authe
189190
*/
190191
@Override
191192
public boolean grants(ClusterPrivilege clusterPrivilege) {
192-
return super.grants(clusterPrivilege) && limitedBy.grants(clusterPrivilege);
193+
return baseRole.grants(clusterPrivilege) && limitedByRole.grants(clusterPrivilege);
193194
}
194195

195196
/**
@@ -212,31 +213,15 @@ public ResourcePrivilegesMap checkApplicationResourcePrivileges(
212213
Set<String> checkForPrivilegeNames,
213214
Collection<ApplicationPrivilegeDescriptor> storedPrivileges
214215
) {
215-
ResourcePrivilegesMap resourcePrivilegesMap = super.application().checkResourcePrivileges(
216-
applicationName,
217-
checkForResources,
218-
checkForPrivilegeNames,
219-
storedPrivileges
220-
);
221-
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedBy.application()
216+
ResourcePrivilegesMap resourcePrivilegesMap = baseRole.application()
217+
.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges);
218+
ResourcePrivilegesMap resourcePrivilegesMapForLimitedRole = limitedByRole.application()
222219
.checkResourcePrivileges(applicationName, checkForResources, checkForPrivilegeNames, storedPrivileges);
223220
return ResourcePrivilegesMap.intersection(resourcePrivilegesMap, resourcePrivilegesMapForLimitedRole);
224221
}
225222

226223
@Override
227224
public boolean checkRunAs(String runAs) {
228-
return super.checkRunAs(runAs) && limitedBy.checkRunAs(runAs);
229-
}
230-
231-
/**
232-
* Create a new role defined by given role and the limited role.
233-
*
234-
* @param fromRole existing role {@link Role}
235-
* @param limitedByRole restrict the newly formed role to the permissions defined by this limited {@link Role}
236-
* @return {@link LimitedRole}
237-
*/
238-
public static LimitedRole createLimitedRole(Role fromRole, Role limitedByRole) {
239-
Objects.requireNonNull(limitedByRole, "limited by role is required to create limited role");
240-
return new LimitedRole(fromRole.cluster(), fromRole.indices(), fromRole.application(), fromRole.runAs(), limitedByRole);
225+
return baseRole.checkRunAs(runAs) && limitedByRole.checkRunAs(runAs);
241226
}
242227
}

0 commit comments

Comments
 (0)