Skip to content

Commit a05b1a7

Browse files
bizybotYogesh Gaikwad
authored andcommitted
HLRC: Create/Update role mapping API (#34171)
We added support for role mapper expression DSL in #33745, that allows us to build the role mapper expression used in the role mapping (as rules for determining user roles based on what the boolean expression resolves to). This change now adds support for create/update role mapping API to the high-level rest client.
1 parent dc526b2 commit a05b1a7

File tree

12 files changed

+569
-4
lines changed

12 files changed

+569
-4
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package org.elasticsearch.client;
2121

2222
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.client.security.PutRoleMappingRequest;
24+
import org.elasticsearch.client.security.PutRoleMappingResponse;
2325
import org.elasticsearch.client.security.DisableUserRequest;
2426
import org.elasticsearch.client.security.EnableUserRequest;
2527
import org.elasticsearch.client.security.GetSslCertificatesRequest;
@@ -75,6 +77,34 @@ public void putUserAsync(PutUserRequest request, RequestOptions options, ActionL
7577
PutUserResponse::fromXContent, listener, emptySet());
7678
}
7779

80+
/**
81+
* Create/Update a role mapping.
82+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html">
83+
* the docs</a> for more.
84+
* @param request the request with the role mapping information
85+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
86+
* @return the response from the put role mapping call
87+
* @throws IOException in case there is a problem sending the request or parsing back the response
88+
*/
89+
public PutRoleMappingResponse putRoleMapping(final PutRoleMappingRequest request, final RequestOptions options) throws IOException {
90+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putRoleMapping, options,
91+
PutRoleMappingResponse::fromXContent, emptySet());
92+
}
93+
94+
/**
95+
* Asynchronously create/update a role mapping.
96+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html">
97+
* the docs</a> for more.
98+
* @param request the request with the role mapping information
99+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
100+
* @param listener the listener to be notified upon request completion
101+
*/
102+
public void putRoleMappingAsync(final PutRoleMappingRequest request, final RequestOptions options,
103+
final ActionListener<PutRoleMappingResponse> listener) {
104+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putRoleMapping, options,
105+
PutRoleMappingResponse::fromXContent, listener, emptySet());
106+
}
107+
78108
/**
79109
* Enable a native realm or built-in user synchronously.
80110
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-enable-user.html">

client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.http.client.methods.HttpPost;
2323
import org.apache.http.client.methods.HttpPut;
24+
import org.elasticsearch.client.security.PutRoleMappingRequest;
2425
import org.elasticsearch.client.security.DisableUserRequest;
2526
import org.elasticsearch.client.security.EnableUserRequest;
2627
import org.elasticsearch.client.security.ChangePasswordRequest;
@@ -61,6 +62,18 @@ static Request putUser(PutUserRequest putUserRequest) throws IOException {
6162
return request;
6263
}
6364

65+
static Request putRoleMapping(final PutRoleMappingRequest putRoleMappingRequest) throws IOException {
66+
final String endpoint = new RequestConverters.EndpointBuilder()
67+
.addPathPartAsIs("_xpack/security/role_mapping")
68+
.addPathPart(putRoleMappingRequest.getName())
69+
.build();
70+
final Request request = new Request(HttpPut.METHOD_NAME, endpoint);
71+
request.setEntity(createEntity(putRoleMappingRequest, REQUEST_BODY_CONTENT_TYPE));
72+
final RequestConverters.Params params = new RequestConverters.Params(request);
73+
params.withRefreshPolicy(putRoleMappingRequest.getRefreshPolicy());
74+
return request;
75+
}
76+
6477
static Request enableUser(EnableUserRequest enableUserRequest) {
6578
return setUserEnabled(enableUserRequest);
6679
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.client.Validatable;
23+
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
24+
import org.elasticsearch.common.Nullable;
25+
import org.elasticsearch.common.Strings;
26+
import org.elasticsearch.common.xcontent.ToXContentObject;
27+
import org.elasticsearch.common.xcontent.XContentBuilder;
28+
29+
import java.io.IOException;
30+
import java.util.Collections;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Objects;
34+
35+
/**
36+
* Request object to create or update a role mapping.
37+
*/
38+
public final class PutRoleMappingRequest implements Validatable, ToXContentObject {
39+
40+
private final String name;
41+
private final boolean enabled;
42+
private final List<String> roles;
43+
private final RoleMapperExpression rules;
44+
45+
private final Map<String, Object> metadata;
46+
private final RefreshPolicy refreshPolicy;
47+
48+
public PutRoleMappingRequest(final String name, final boolean enabled, final List<String> roles, final RoleMapperExpression rules,
49+
@Nullable final Map<String, Object> metadata, @Nullable final RefreshPolicy refreshPolicy) {
50+
if (Strings.hasText(name) == false) {
51+
throw new IllegalArgumentException("role-mapping name is missing");
52+
}
53+
this.name = name;
54+
this.enabled = enabled;
55+
if (roles == null || roles.isEmpty()) {
56+
throw new IllegalArgumentException("role-mapping roles are missing");
57+
}
58+
this.roles = Collections.unmodifiableList(roles);
59+
this.rules = Objects.requireNonNull(rules, "role-mapping rules are missing");
60+
this.metadata = (metadata == null) ? Collections.emptyMap() : metadata;
61+
this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy;
62+
}
63+
64+
public String getName() {
65+
return name;
66+
}
67+
68+
public boolean isEnabled() {
69+
return enabled;
70+
}
71+
72+
public List<String> getRoles() {
73+
return roles;
74+
}
75+
76+
public RoleMapperExpression getRules() {
77+
return rules;
78+
}
79+
80+
public Map<String, Object> getMetadata() {
81+
return metadata;
82+
}
83+
84+
public RefreshPolicy getRefreshPolicy() {
85+
return refreshPolicy;
86+
}
87+
88+
@Override
89+
public int hashCode() {
90+
return Objects.hash(name, enabled, refreshPolicy, roles, rules, metadata);
91+
}
92+
93+
@Override
94+
public boolean equals(Object obj) {
95+
if (this == obj) {
96+
return true;
97+
}
98+
if (obj == null) {
99+
return false;
100+
}
101+
if (getClass() != obj.getClass()) {
102+
return false;
103+
}
104+
final PutRoleMappingRequest other = (PutRoleMappingRequest) obj;
105+
106+
return (enabled == other.enabled) &&
107+
(refreshPolicy == other.refreshPolicy) &&
108+
Objects.equals(name, other.name) &&
109+
Objects.equals(roles, other.roles) &&
110+
Objects.equals(rules, other.rules) &&
111+
Objects.equals(metadata, other.metadata);
112+
}
113+
114+
@Override
115+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
116+
builder.startObject();
117+
builder.field("enabled", enabled);
118+
builder.field("roles", roles);
119+
builder.field("rules", rules);
120+
builder.field("metadata", metadata);
121+
return builder.endObject();
122+
}
123+
124+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.XContentParser;
25+
26+
import java.io.IOException;
27+
import java.util.Objects;
28+
29+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
30+
31+
/**
32+
* Response when adding/updating a role mapping. Returns a boolean field for
33+
* whether the role mapping was created or updated.
34+
*/
35+
public final class PutRoleMappingResponse {
36+
37+
private final boolean created;
38+
39+
public PutRoleMappingResponse(boolean created) {
40+
this.created = created;
41+
}
42+
43+
public boolean isCreated() {
44+
return created;
45+
}
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (this == o) {
50+
return true;
51+
}
52+
if (o == null || getClass() != o.getClass()) {
53+
return false;
54+
}
55+
final PutRoleMappingResponse that = (PutRoleMappingResponse) o;
56+
return created == that.created;
57+
}
58+
59+
@Override
60+
public int hashCode() {
61+
return Objects.hash(created);
62+
}
63+
64+
private static final ConstructingObjectParser<PutRoleMappingResponse, Void> PARSER = new ConstructingObjectParser<>(
65+
"put_role_mapping_response", true, args -> new PutRoleMappingResponse((boolean) args[0]));
66+
static {
67+
PARSER.declareBoolean(constructorArg(), new ParseField("created"));
68+
// To parse the "created" field we declare "role_mapping" field object.
69+
// Once the nested field "created" is found parser constructs the target object and
70+
// ignores the role_mapping object.
71+
PARSER.declareObject((a,b) -> {}, (parser, context) -> null, new ParseField("role_mapping"));
72+
}
73+
74+
public static PutRoleMappingResponse fromXContent(XContentParser parser) throws IOException {
75+
return PARSER.parse(parser, null);
76+
}
77+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/expressiondsl/expressions/CompositeRoleMapperExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public abstract class CompositeRoleMapperExpression implements RoleMapperExpress
5757
}
5858

5959
public String getName() {
60-
return this.getName();
60+
return this.name;
6161
}
6262

6363
public List<RoleMapperExpression> getElements() {

client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/expressiondsl/fields/FieldRoleMapperExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public FieldRoleMapperExpression(final String field, final Object... values) {
5050
throw new IllegalArgumentException("null or empty field name (" + field + ")");
5151
}
5252
if (values == null || values.length == 0) {
53-
throw new IllegalArgumentException("null or empty values (" + values + ")");
53+
throw new IllegalArgumentException("null or empty values for field (" + field + ")");
5454
}
5555
this.field = field;
5656
this.values = Collections.unmodifiableList(Arrays.asList(values));

client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
import org.elasticsearch.client.security.DisableUserRequest;
2525
import org.elasticsearch.client.security.EnableUserRequest;
2626
import org.elasticsearch.client.security.ChangePasswordRequest;
27+
import org.elasticsearch.client.security.PutRoleMappingRequest;
2728
import org.elasticsearch.client.security.PutUserRequest;
2829
import org.elasticsearch.client.security.RefreshPolicy;
30+
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
31+
import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
32+
import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression;
2933
import org.elasticsearch.test.ESTestCase;
3034

3135
import java.io.IOException;
@@ -67,6 +71,34 @@ public void testPutUser() throws IOException {
6771
assertToXContentBody(putUserRequest, request.getEntity());
6872
}
6973

74+
public void testPutRoleMapping() throws IOException {
75+
final String username = randomAlphaOfLengthBetween(4, 7);
76+
final String rolename = randomAlphaOfLengthBetween(4, 7);
77+
final String roleMappingName = randomAlphaOfLengthBetween(4, 7);
78+
final String groupname = "cn="+randomAlphaOfLengthBetween(4, 7)+",dc=example,dc=com";
79+
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
80+
final Map<String, String> expectedParams;
81+
if (refreshPolicy != RefreshPolicy.NONE) {
82+
expectedParams = Collections.singletonMap("refresh", refreshPolicy.getValue());
83+
} else {
84+
expectedParams = Collections.emptyMap();
85+
}
86+
87+
final RoleMapperExpression rules = AnyRoleMapperExpression.builder()
88+
.addExpression(FieldRoleMapperExpression.ofUsername(username))
89+
.addExpression(FieldRoleMapperExpression.ofGroups(groupname))
90+
.build();
91+
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(roleMappingName, true, Collections.singletonList(
92+
rolename), rules, null, refreshPolicy);
93+
94+
final Request request = SecurityRequestConverters.putRoleMapping(putRoleMappingRequest);
95+
96+
assertEquals(HttpPut.METHOD_NAME, request.getMethod());
97+
assertEquals("/_xpack/security/role_mapping/" + roleMappingName, request.getEndpoint());
98+
assertEquals(expectedParams, request.getParameters());
99+
assertToXContentBody(putRoleMappingRequest, request.getEntity());
100+
}
101+
70102
public void testEnableUser() {
71103
final String username = randomAlphaOfLengthBetween(1, 12);
72104
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());

0 commit comments

Comments
 (0)