Skip to content

Commit 54ef24d

Browse files
[HLRC] Put Role (#36209)
This commit adds support for the put role API in the java high level rest client.
1 parent f9bd94e commit 54ef24d

18 files changed

+667
-88
lines changed

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
import org.elasticsearch.client.security.PutPrivilegesResponse;
5757
import org.elasticsearch.client.security.PutRoleMappingRequest;
5858
import org.elasticsearch.client.security.PutRoleMappingResponse;
59+
import org.elasticsearch.client.security.PutRoleRequest;
60+
import org.elasticsearch.client.security.PutRoleResponse;
5961
import org.elasticsearch.client.security.PutUserRequest;
6062
import org.elasticsearch.client.security.PutUserResponse;
6163

@@ -461,14 +463,43 @@ public void getRolesAsync(GetRolesRequest request, RequestOptions options, Actio
461463
*
462464
* @param request the request with the roles to get
463465
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
464-
* @return the response from the delete role call
466+
* @return the response from the get roles call
465467
* @throws IOException in case there is a problem sending the request or parsing back the response
466468
*/
467469
public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOptions options) throws IOException {
468470
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getRoles, options,
469471
GetRolesResponse::fromXContent, emptySet());
470472
}
471473

474+
/**
475+
* Asynchronously creates or updates a role in the native roles store.
476+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role.html">
477+
* the docs</a> for more.
478+
*
479+
* @param request the request containing the role to create or update
480+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
481+
* @param listener the listener to be notified upon request completion
482+
*/
483+
public void putRoleAsync(PutRoleRequest request, RequestOptions options, ActionListener<PutRoleResponse> listener) {
484+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putRole, options,
485+
PutRoleResponse::fromXContent, listener, emptySet());
486+
}
487+
488+
/**
489+
* Create or update a role in the native roles store.
490+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role.html">
491+
* the docs</a> for more.
492+
*
493+
* @param request the request containing the role to create or update
494+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
495+
* @return the response from the put role call
496+
* @throws IOException in case there is a problem sending the request or parsing back the response
497+
*/
498+
public PutRoleResponse putRole(final PutRoleRequest request, final RequestOptions options) throws IOException {
499+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putRole, options,
500+
PutRoleResponse::fromXContent, emptySet());
501+
}
502+
472503
/**
473504
* Asynchronously delete a role mapping.
474505
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delete-role-mapping.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
@@ -40,6 +40,7 @@
4040
import org.elasticsearch.client.security.InvalidateTokenRequest;
4141
import org.elasticsearch.client.security.PutPrivilegesRequest;
4242
import org.elasticsearch.client.security.PutRoleMappingRequest;
43+
import org.elasticsearch.client.security.PutRoleRequest;
4344
import org.elasticsearch.client.security.PutUserRequest;
4445
import org.elasticsearch.client.security.SetUserEnabledRequest;
4546
import org.elasticsearch.common.Strings;
@@ -233,4 +234,16 @@ static Request deletePrivileges(DeletePrivilegesRequest deletePrivilegeRequest)
233234
params.withRefreshPolicy(deletePrivilegeRequest.getRefreshPolicy());
234235
return request;
235236
}
237+
238+
static Request putRole(final PutRoleRequest putRoleRequest) throws IOException {
239+
final String endpoint = new RequestConverters.EndpointBuilder()
240+
.addPathPartAsIs("_xpack/security/role")
241+
.addPathPart(putRoleRequest.getRole().getName())
242+
.build();
243+
final Request request = new Request(HttpPut.METHOD_NAME, endpoint);
244+
request.setEntity(createEntity(putRoleRequest, REQUEST_BODY_CONTENT_TYPE));
245+
final RequestConverters.Params params = new RequestConverters.Params(request);
246+
params.withRefreshPolicy(putRoleRequest.getRefreshPolicy());
247+
return request;
248+
}
236249
}

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020
package org.elasticsearch.client.security;
2121

2222
import org.elasticsearch.client.security.user.privileges.Role;
23+
import org.elasticsearch.common.collect.Tuple;
2324
import org.elasticsearch.common.xcontent.XContentParser;
2425
import org.elasticsearch.common.xcontent.XContentParserUtils;
2526

2627
import java.io.IOException;
2728
import java.util.ArrayList;
2829
import java.util.Collections;
30+
import java.util.HashMap;
2931
import java.util.List;
32+
import java.util.Map;
3033
import java.util.Objects;
3134

3235
/**
@@ -36,36 +39,50 @@
3639
public final class GetRolesResponse {
3740

3841
private final List<Role> roles;
42+
private final Map<String, Map<String, Object>> transientMetadataMap;
3943

40-
public GetRolesResponse(List<Role> roles) {
44+
GetRolesResponse(List<Role> roles, Map<String, Map<String, Object>> transientMetadataMap) {
4145
this.roles = Collections.unmodifiableList(roles);
46+
this.transientMetadataMap = Collections.unmodifiableMap(transientMetadataMap);
4247
}
4348

4449
public List<Role> getRoles() {
4550
return roles;
4651
}
4752

53+
public Map<String, Map<String, Object>> getTransientMetadataMap() {
54+
return transientMetadataMap;
55+
}
56+
57+
public Map<String, Object> getTransientMetadata(String roleName) {
58+
return transientMetadataMap.get(roleName);
59+
}
60+
4861
public static GetRolesResponse fromXContent(XContentParser parser) throws IOException {
4962
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
5063
final List<Role> roles = new ArrayList<>();
64+
final Map<String, Map<String, Object>> transientMetadata = new HashMap<>();
5165
XContentParser.Token token;
5266
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
5367
XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
54-
roles.add(Role.PARSER.parse(parser, parser.currentName()));
68+
final Tuple<Role, Map<String, Object>> roleAndTransientMetadata = Role.PARSER.parse(parser, parser.currentName());
69+
roles.add(roleAndTransientMetadata.v1());
70+
transientMetadata.put(roleAndTransientMetadata.v1().getName(), roleAndTransientMetadata.v2());
5571
}
56-
return new GetRolesResponse(roles);
72+
return new GetRolesResponse(roles, transientMetadata);
5773
}
5874

5975
@Override
6076
public boolean equals(Object o) {
6177
if (this == o) return true;
6278
if (o == null || getClass() != o.getClass()) return false;
6379
GetRolesResponse response = (GetRolesResponse) o;
64-
return Objects.equals(roles, response.roles);
80+
return Objects.equals(roles, response.roles)
81+
&& Objects.equals(transientMetadataMap, response.transientMetadataMap);
6582
}
6683

6784
@Override
6885
public int hashCode() {
69-
return Objects.hash(roles);
86+
return Objects.hash(roles, transientMetadataMap);
7087
}
7188
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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.user.privileges.Role;
24+
import org.elasticsearch.common.Nullable;
25+
import org.elasticsearch.common.xcontent.ToXContentObject;
26+
import org.elasticsearch.common.xcontent.XContentBuilder;
27+
28+
import java.io.IOException;
29+
import java.util.Objects;
30+
31+
/**
32+
* Request object to create or update a role.
33+
*/
34+
public final class PutRoleRequest implements Validatable, ToXContentObject {
35+
36+
private final Role role;
37+
private final RefreshPolicy refreshPolicy;
38+
39+
public PutRoleRequest(Role role, @Nullable final RefreshPolicy refreshPolicy) {
40+
this.role = Objects.requireNonNull(role);
41+
this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy;
42+
}
43+
44+
public Role getRole() {
45+
return role;
46+
}
47+
48+
public RefreshPolicy getRefreshPolicy() {
49+
return refreshPolicy;
50+
}
51+
52+
@Override
53+
public int hashCode() {
54+
return Objects.hash(role, refreshPolicy);
55+
}
56+
57+
@Override
58+
public boolean equals(Object obj) {
59+
if (this == obj) {
60+
return true;
61+
}
62+
if (obj == null) {
63+
return false;
64+
}
65+
if (getClass() != obj.getClass()) {
66+
return false;
67+
}
68+
final PutRoleRequest other = (PutRoleRequest) obj;
69+
70+
return (refreshPolicy == other.getRefreshPolicy()) &&
71+
Objects.equals(role, other.role);
72+
}
73+
74+
@Override
75+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
76+
builder.startObject();
77+
if (role.getApplicationResourcePrivileges() != null) {
78+
builder.field(Role.APPLICATIONS.getPreferredName(), role.getApplicationResourcePrivileges());
79+
}
80+
if (role.getClusterPrivileges() != null) {
81+
builder.field(Role.CLUSTER.getPreferredName(), role.getClusterPrivileges());
82+
}
83+
if (role.getGlobalApplicationPrivileges() != null) {
84+
builder.field(Role.GLOBAL.getPreferredName(), role.getGlobalApplicationPrivileges());
85+
}
86+
if (role.getIndicesPrivileges() != null) {
87+
builder.field(Role.INDICES.getPreferredName(), role.getIndicesPrivileges());
88+
}
89+
if (role.getMetadata() != null) {
90+
builder.field(Role.METADATA.getPreferredName(), role.getMetadata());
91+
}
92+
if (role.getRunAsPrivilege() != null) {
93+
builder.field(Role.RUN_AS.getPreferredName(), role.getRunAsPrivilege());
94+
}
95+
return builder.endObject();
96+
}
97+
98+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
import org.elasticsearch.common.xcontent.XContentParser.Token;
26+
27+
import java.io.IOException;
28+
import java.util.Objects;
29+
30+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
31+
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
32+
import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
33+
34+
/**
35+
* Response when adding a role to the native roles store. Returns a
36+
* single boolean field for whether the role was created (true) or updated (false).
37+
*/
38+
public final class PutRoleResponse {
39+
40+
private final boolean created;
41+
42+
public PutRoleResponse(boolean created) {
43+
this.created = created;
44+
}
45+
46+
public boolean isCreated() {
47+
return created;
48+
}
49+
50+
@Override
51+
public boolean equals(Object o) {
52+
if (this == o) return true;
53+
if (o == null || getClass() != o.getClass()) return false;
54+
PutRoleResponse that = (PutRoleResponse) o;
55+
return created == that.created;
56+
}
57+
58+
@Override
59+
public int hashCode() {
60+
return Objects.hash(created);
61+
}
62+
63+
private static final ConstructingObjectParser<PutRoleResponse, Void> PARSER = new ConstructingObjectParser<>("put_role_response",
64+
true, args -> new PutRoleResponse((boolean) args[0]));
65+
66+
static {
67+
PARSER.declareBoolean(constructorArg(), new ParseField("created"));
68+
}
69+
70+
public static PutRoleResponse fromXContent(XContentParser parser) throws IOException {
71+
if (parser.currentToken() == null) {
72+
parser.nextToken();
73+
}
74+
// parse extraneous wrapper
75+
ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
76+
ensureFieldName(parser, parser.nextToken(), "role");
77+
parser.nextToken();
78+
final PutRoleResponse roleResponse = PARSER.parse(parser, null);
79+
ensureExpectedToken(Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation);
80+
return roleResponse;
81+
}
82+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import org.elasticsearch.common.xcontent.XContentParser;
2727

2828
import java.io.IOException;
29-
import java.util.Arrays;
3029
import java.util.Collection;
3130
import java.util.Collections;
3231
import java.util.HashSet;
@@ -49,7 +48,7 @@ public final class GlobalPrivileges implements ToXContentObject {
4948

5049
// When categories change, adapting this field should suffice. Categories are NOT
5150
// opaque "named_objects", we wish to maintain control over these namespaces
52-
static final List<String> CATEGORIES = Collections.unmodifiableList(Arrays.asList("application"));
51+
public static final List<String> CATEGORIES = Collections.singletonList("application");
5352

5453
@SuppressWarnings("unchecked")
5554
static final ConstructingObjectParser<GlobalPrivileges, Void> PARSER = new ConstructingObjectParser<>("global_category_privileges",
@@ -134,4 +133,4 @@ public int hashCode() {
134133
return Objects.hash(privileges);
135134
}
136135

137-
}
136+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
217217
builder.startObject();
218218
builder.field(NAMES.getPreferredName(), indices);
219219
builder.field(PRIVILEGES.getPreferredName(), privileges);
220-
if (isUsingFieldLevelSecurity()) {
220+
if (grantedFields != null || deniedFields != null) {
221221
builder.startObject(FIELD_PERMISSIONS.getPreferredName());
222222
if (grantedFields != null) {
223223
builder.field(GRANT_FIELDS.getPreferredName(), grantedFields);
224224
}
225-
if (hasDeniedFields()) {
225+
if (deniedFields != null) {
226226
builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields);
227227
}
228228
builder.endObject();

0 commit comments

Comments
 (0)