diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
index 811b9c9053ee8..d1a74030992ac 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
@@ -56,6 +56,8 @@
import org.elasticsearch.client.security.PutPrivilegesResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleMappingResponse;
+import org.elasticsearch.client.security.PutRoleRequest;
+import org.elasticsearch.client.security.PutRoleResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
@@ -461,7 +463,7 @@ public void getRolesAsync(GetRolesRequest request, RequestOptions options, Actio
*
* @param request the request with the roles to get
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
- * @return the response from the delete role call
+ * @return the response from the get roles call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOptions options) throws IOException {
@@ -469,6 +471,35 @@ public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOpt
GetRolesResponse::fromXContent, emptySet());
}
+ /**
+ * Asynchronously creates or updates a role in the native roles store.
+ * See
+ * the docs for more.
+ *
+ * @param request the request containing the role to create or update
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void putRoleAsync(PutRoleRequest request, RequestOptions options, ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putRole, options,
+ PutRoleResponse::fromXContent, listener, emptySet());
+ }
+
+ /**
+ * Create or update a role in the native roles store.
+ * See
+ * the docs for more.
+ *
+ * @param request the request containing the role to create or update
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the put role call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public PutRoleResponse putRole(final PutRoleRequest request, final RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putRole, options,
+ PutRoleResponse::fromXContent, emptySet());
+ }
+
/**
* Asynchronously delete a role mapping.
* See
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
index d893c20c3b2fd..a37966d0ff59a 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
@@ -40,6 +40,7 @@
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.PutPrivilegesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
+import org.elasticsearch.client.security.PutRoleRequest;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.SetUserEnabledRequest;
import org.elasticsearch.common.Strings;
@@ -233,4 +234,16 @@ static Request deletePrivileges(DeletePrivilegesRequest deletePrivilegeRequest)
params.withRefreshPolicy(deletePrivilegeRequest.getRefreshPolicy());
return request;
}
+
+ static Request putRole(final PutRoleRequest putRoleRequest) throws IOException {
+ final String endpoint = new RequestConverters.EndpointBuilder()
+ .addPathPartAsIs("_xpack/security/role")
+ .addPathPart(putRoleRequest.getRole().getName())
+ .build();
+ final Request request = new Request(HttpPut.METHOD_NAME, endpoint);
+ request.setEntity(createEntity(putRoleRequest, REQUEST_BODY_CONTENT_TYPE));
+ final RequestConverters.Params params = new RequestConverters.Params(request);
+ params.withRefreshPolicy(putRoleRequest.getRefreshPolicy());
+ return request;
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java
index 91b7527c3235e..1a937ea5cfeee 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java
@@ -20,13 +20,16 @@
package org.elasticsearch.client.security;
import org.elasticsearch.client.security.user.privileges.Role;
+import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentParserUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
/**
@@ -36,24 +39,37 @@
public final class GetRolesResponse {
private final List roles;
+ private final Map> transientMetadataMap;
- public GetRolesResponse(List roles) {
+ GetRolesResponse(List roles, Map> transientMetadataMap) {
this.roles = Collections.unmodifiableList(roles);
+ this.transientMetadataMap = Collections.unmodifiableMap(transientMetadataMap);
}
public List getRoles() {
return roles;
}
+ public Map> getTransientMetadataMap() {
+ return transientMetadataMap;
+ }
+
+ public Map getTransientMetadata(String roleName) {
+ return transientMetadataMap.get(roleName);
+ }
+
public static GetRolesResponse fromXContent(XContentParser parser) throws IOException {
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation);
final List roles = new ArrayList<>();
+ final Map> transientMetadata = new HashMap<>();
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation);
- roles.add(Role.PARSER.parse(parser, parser.currentName()));
+ final Tuple> roleAndTransientMetadata = Role.PARSER.parse(parser, parser.currentName());
+ roles.add(roleAndTransientMetadata.v1());
+ transientMetadata.put(roleAndTransientMetadata.v1().getName(), roleAndTransientMetadata.v2());
}
- return new GetRolesResponse(roles);
+ return new GetRolesResponse(roles, transientMetadata);
}
@Override
@@ -61,11 +77,12 @@ public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GetRolesResponse response = (GetRolesResponse) o;
- return Objects.equals(roles, response.roles);
+ return Objects.equals(roles, response.roles)
+ && Objects.equals(transientMetadataMap, response.transientMetadataMap);
}
@Override
public int hashCode() {
- return Objects.hash(roles);
+ return Objects.hash(roles, transientMetadataMap);
}
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java
new file mode 100644
index 0000000000000..da9106744bac4
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.client.security.user.privileges.Role;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * Request object to create or update a role.
+ */
+public final class PutRoleRequest implements Validatable, ToXContentObject {
+
+ private final Role role;
+ private final RefreshPolicy refreshPolicy;
+
+ public PutRoleRequest(Role role, @Nullable final RefreshPolicy refreshPolicy) {
+ this.role = Objects.requireNonNull(role);
+ this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public RefreshPolicy getRefreshPolicy() {
+ return refreshPolicy;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(role, refreshPolicy);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final PutRoleRequest other = (PutRoleRequest) obj;
+
+ return (refreshPolicy == other.getRefreshPolicy()) &&
+ Objects.equals(role, other.role);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ if (role.getApplicationResourcePrivileges() != null) {
+ builder.field(Role.APPLICATIONS.getPreferredName(), role.getApplicationResourcePrivileges());
+ }
+ if (role.getClusterPrivileges() != null) {
+ builder.field(Role.CLUSTER.getPreferredName(), role.getClusterPrivileges());
+ }
+ if (role.getGlobalApplicationPrivileges() != null) {
+ builder.field(Role.GLOBAL.getPreferredName(), role.getGlobalApplicationPrivileges());
+ }
+ if (role.getIndicesPrivileges() != null) {
+ builder.field(Role.INDICES.getPreferredName(), role.getIndicesPrivileges());
+ }
+ if (role.getMetadata() != null) {
+ builder.field(Role.METADATA.getPreferredName(), role.getMetadata());
+ }
+ if (role.getRunAsPrivilege() != null) {
+ builder.field(Role.RUN_AS.getPreferredName(), role.getRunAsPrivilege());
+ }
+ return builder.endObject();
+ }
+
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java
new file mode 100644
index 0000000000000..b42782b7d9430
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParser.Token;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
+import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName;
+
+/**
+ * Response when adding a role to the native roles store. Returns a
+ * single boolean field for whether the role was created (true) or updated (false).
+ */
+public final class PutRoleResponse {
+
+ private final boolean created;
+
+ public PutRoleResponse(boolean created) {
+ this.created = created;
+ }
+
+ public boolean isCreated() {
+ return created;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PutRoleResponse that = (PutRoleResponse) o;
+ return created == that.created;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(created);
+ }
+
+ private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("put_role_response",
+ true, args -> new PutRoleResponse((boolean) args[0]));
+
+ static {
+ PARSER.declareBoolean(constructorArg(), new ParseField("created"));
+ }
+
+ public static PutRoleResponse fromXContent(XContentParser parser) throws IOException {
+ if (parser.currentToken() == null) {
+ parser.nextToken();
+ }
+ // parse extraneous wrapper
+ ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
+ ensureFieldName(parser, parser.nextToken(), "role");
+ parser.nextToken();
+ final PutRoleResponse roleResponse = PARSER.parse(parser, null);
+ ensureExpectedToken(Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation);
+ return roleResponse;
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java
index 891980765427e..c1bd5550eca1a 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java
@@ -26,7 +26,6 @@
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@@ -49,7 +48,7 @@ public final class GlobalPrivileges implements ToXContentObject {
// When categories change, adapting this field should suffice. Categories are NOT
// opaque "named_objects", we wish to maintain control over these namespaces
- static final List CATEGORIES = Collections.unmodifiableList(Arrays.asList("application"));
+ public static final List CATEGORIES = Collections.singletonList("application");
@SuppressWarnings("unchecked")
static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("global_category_privileges",
@@ -134,4 +133,4 @@ public int hashCode() {
return Objects.hash(privileges);
}
-}
\ No newline at end of file
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java
index 393b8613f25e7..188b8b4ea14dc 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java
@@ -217,12 +217,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.startObject();
builder.field(NAMES.getPreferredName(), indices);
builder.field(PRIVILEGES.getPreferredName(), privileges);
- if (isUsingFieldLevelSecurity()) {
+ if (grantedFields != null || deniedFields != null) {
builder.startObject(FIELD_PERMISSIONS.getPreferredName());
if (grantedFields != null) {
builder.field(GRANT_FIELDS.getPreferredName(), grantedFields);
}
- if (hasDeniedFields()) {
+ if (deniedFields != null) {
builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields);
}
builder.endObject();
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
index e332971a512fd..27af02fdf50db 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
@@ -22,6 +22,7 @@
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
@@ -34,7 +35,6 @@
import java.util.Objects;
import java.util.Set;
-import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
@@ -51,8 +51,8 @@ public final class Role {
public static final ParseField TRANSIENT_METADATA = new ParseField("transient_metadata");
@SuppressWarnings("unchecked")
- public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("role_descriptor", false,
- (constructorObjects, roleName) -> {
+ public static final ConstructingObjectParser>, String> PARSER =
+ new ConstructingObjectParser<>("role_descriptor", false, (constructorObjects, roleName) -> {
// Don't ignore unknown fields. It is dangerous if the object we parse is also
// part of a request that we build later on, and the fields that we now ignore
// will end up being implicitly set to null in that request.
@@ -65,8 +65,10 @@ public final class Role {
final Collection runAsPrivilege = (Collection) constructorObjects[i++];
final Map metadata = (Map) constructorObjects[i++];
final Map transientMetadata = (Map) constructorObjects[i];
- return new Role(roleName, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
- runAsPrivilege, metadata, transientMetadata);
+ return new Tuple<>(
+ new Role(roleName, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
+ runAsPrivilege, metadata),
+ transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) : Collections.emptyMap());
});
static {
@@ -77,8 +79,8 @@ public final class Role {
PARSER.declareFieldArray(optionalConstructorArg(), (parser,c)->ApplicationResourcePrivileges.PARSER.parse(parser,null),
APPLICATIONS, ValueType.OBJECT_ARRAY);
PARSER.declareStringArray(optionalConstructorArg(), RUN_AS);
- PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
- PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), TRANSIENT_METADATA);
+ PARSER.declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), METADATA);
+ PARSER.declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), TRANSIENT_METADATA);
}
private final String name;
@@ -88,14 +90,12 @@ public final class Role {
private final Set applicationResourcePrivileges;
private final Set runAsPrivilege;
private final Map metadata;
- private final Map transientMetadata;
private Role(String name, @Nullable Collection clusterPrivileges,
@Nullable GlobalPrivileges globalApplicationPrivileges,
@Nullable Collection indicesPrivileges,
@Nullable Collection applicationResourcePrivileges,
- @Nullable Collection runAsPrivilege, @Nullable Map metadata,
- @Nullable Map transientMetadata) {
+ @Nullable Collection runAsPrivilege, @Nullable Map metadata) {
if (Strings.hasText(name) == false){
throw new IllegalArgumentException("role name must be provided");
} else {
@@ -114,7 +114,6 @@ private Role(String name, @Nullable Collection clusterPrivileges,
// no run as privileges are granted unless otherwise specified
this.runAsPrivilege = Collections.unmodifiableSet(runAsPrivilege != null ? new HashSet<>(runAsPrivilege) : Collections.emptySet());
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
- this.transientMetadata = transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) : Collections.emptyMap();
}
public String getName() {
@@ -156,14 +155,13 @@ public boolean equals(Object o) {
&& indicesPrivileges.equals(that.indicesPrivileges)
&& applicationResourcePrivileges.equals(that.applicationResourcePrivileges)
&& runAsPrivilege.equals(that.runAsPrivilege)
- && metadata.equals(that.metadata)
- && transientMetadata.equals(that.transientMetadata);
+ && metadata.equals(that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
- runAsPrivilege, metadata, transientMetadata);
+ runAsPrivilege, metadata);
}
@Override
@@ -200,16 +198,11 @@ public String toString() {
sb.append(metadata.toString());
sb.append("], ");
}
- if (false == transientMetadata.isEmpty()) {
- sb.append("TransientMetadata=[");
- sb.append(transientMetadata.toString());
- sb.append("] ");
- }
sb.append("}");
return sb.toString();
}
- public static Role fromXContent(XContentParser parser, String name) {
+ public static Tuple> fromXContent(XContentParser parser, String name) {
return PARSER.apply(parser, name);
}
@@ -226,7 +219,6 @@ public static final class Builder {
private @Nullable Collection applicationResourcePrivileges = null;
private @Nullable Collection runAsPrivilege = null;
private @Nullable Map metadata = null;
- private @Nullable Map transientMetadata = null;
private Builder() {
}
@@ -294,15 +286,9 @@ public Builder metadata(Map metadata) {
return this;
}
- public Builder transientMetadata(Map transientMetadata) {
- this.transientMetadata =
- Objects.requireNonNull(transientMetadata, "Transient metadata cannot be null. Pass an empty map instead.");
- return this;
- }
-
public Role build() {
return new Role(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
- runAsPrivilege, metadata, transientMetadata);
+ runAsPrivilege, metadata);
}
}
@@ -329,6 +315,9 @@ public static class ClusterPrivilegeName {
public static final String MANAGE_PIPELINE = "manage_pipeline";
public static final String MANAGE_CCR = "manage_ccr";
public static final String READ_CCR = "read_ccr";
+ public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE,
+ MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT,
+ MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR };
}
/**
@@ -349,6 +338,8 @@ public static class IndexPrivilegeName {
public static final String CREATE_INDEX = "create_index";
public static final String VIEW_INDEX_METADATA = "view_index_metadata";
public static final String MANAGE_FOLLOW_INDEX = "manage_follow_index";
+ public static final String[] ALL_ARRAY = new String[] { NONE, ALL, READ, READ_CROSS, CREATE, INDEX, DELETE, WRITE, MONITOR, MANAGE,
+ DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX };
}
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java
index 27b1d31e6d7d5..466cc5b0135dc 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java
@@ -22,22 +22,36 @@
import org.apache.http.client.methods.HttpDelete;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.client.security.AuthenticateResponse;
+import org.elasticsearch.client.security.DeleteRoleRequest;
+import org.elasticsearch.client.security.DeleteRoleResponse;
import org.elasticsearch.client.security.DeleteUserRequest;
import org.elasticsearch.client.security.DeleteUserResponse;
+import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.GetRolesResponse;
+import org.elasticsearch.client.security.PutRoleRequest;
+import org.elasticsearch.client.security.PutRoleResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
+import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.GlobalPrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.Role;
import org.elasticsearch.common.CharArrays;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.contains;
public class SecurityIT extends ESRestHighLevelClientTestCase {
@@ -94,6 +108,31 @@ public void testAuthenticate() throws Exception {
assertThat(deleteUserResponse2.isAcknowledged(), is(false));
}
+ public void testPutRole() throws Exception {
+ final SecurityClient securityClient = highLevelClient().security();
+ // create random role
+ final Role role = randomRole(randomAlphaOfLength(4));
+ final PutRoleRequest putRoleRequest = new PutRoleRequest(role, RefreshPolicy.IMMEDIATE);
+
+ final PutRoleResponse createRoleResponse = execute(putRoleRequest, securityClient::putRole, securityClient::putRoleAsync);
+ // assert role created
+ assertThat(createRoleResponse.isCreated(), is(true));
+
+ final GetRolesRequest getRoleRequest = new GetRolesRequest(role.getName());
+ final GetRolesResponse getRoleResponse = securityClient.getRoles(getRoleRequest, RequestOptions.DEFAULT);
+ // assert role is equal
+ assertThat(getRoleResponse.getRoles(), contains(role));
+
+ final PutRoleResponse updateRoleResponse = execute(putRoleRequest, securityClient::putRole, securityClient::putRoleAsync);
+ // assert role updated
+ assertThat(updateRoleResponse.isCreated(), is(false));
+
+ final DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest(role.getName());
+ final DeleteRoleResponse deleteRoleResponse = securityClient.deleteRole(deleteRoleRequest, RequestOptions.DEFAULT);
+ // assert role deleted
+ assertThat(deleteRoleResponse.isFound(), is(true));
+ }
+
private static User randomUser() {
final String username = randomAlphaOfLengthBetween(1, 4);
return randomUser(username);
@@ -118,6 +157,28 @@ private static User randomUser(String username) {
return new User(username, roles, metadata, fullName, email);
}
+ private static Role randomRole(String roleName) {
+ final Role.Builder roleBuilder = Role.builder()
+ .name(roleName)
+ .clusterPrivileges(randomSubsetOf(randomInt(3), Role.ClusterPrivilegeName.ALL_ARRAY))
+ .indicesPrivileges(
+ randomArray(3, IndicesPrivileges[]::new, () -> IndicesPrivilegesTests.createNewRandom(randomAlphaOfLength(3))))
+ .applicationResourcePrivileges(randomArray(3, ApplicationResourcePrivileges[]::new,
+ () -> ApplicationResourcePrivilegesTests.createNewRandom(randomAlphaOfLength(3).toLowerCase(Locale.ROOT))))
+ .runAsPrivilege(randomArray(3, String[]::new, () -> randomAlphaOfLength(3)));
+ if (randomBoolean()) {
+ roleBuilder.globalApplicationPrivileges(GlobalPrivilegesTests.buildRandomManageApplicationPrivilege());
+ }
+ if (randomBoolean()) {
+ final Map metadata = new HashMap<>();
+ for (int i = 0; i < randomInt(3); i++) {
+ metadata.put(randomAlphaOfLength(3), randomAlphaOfLength(3));
+ }
+ roleBuilder.metadata(metadata);
+ }
+ return roleBuilder.build();
+ }
+
private static PutUserRequest randomPutUserRequest(boolean enabled) {
final User user = randomUser();
return randomPutUserRequest(user, enabled);
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
index f5015c1203dea..489b52468eac6 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
@@ -36,13 +36,17 @@
import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.PutPrivilegesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
+import org.elasticsearch.client.security.PutRoleRequest;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression;
import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression;
import org.elasticsearch.client.security.user.User;
+import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
import org.elasticsearch.client.security.user.privileges.ApplicationPrivilege;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
+import org.elasticsearch.client.security.user.privileges.Role;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
@@ -357,4 +361,37 @@ public void testDeletePrivileges() {
assertEquals(expectedParams, request.getParameters());
assertNull(request.getEntity());
}
+
+ public void testPutRole() throws IOException {
+ final String roleName = randomAlphaOfLengthBetween(4, 7);
+ final List clusterPrivileges = randomSubsetOf(3, Role.ClusterPrivilegeName.ALL_ARRAY);
+ final Map metadata = Collections.singletonMap(randomAlphaOfLengthBetween(4, 7), randomAlphaOfLengthBetween(4, 7));
+ final String[] runAsPrivilege = randomArray(3, String[]::new, () -> randomAlphaOfLength(5));
+ final List applicationPrivilegeNames = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5)));
+ final List applicationResouceNames = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5)));
+ final ApplicationResourcePrivileges applicationResourcePrivilege = new ApplicationResourcePrivileges(
+ randomAlphaOfLengthBetween(4, 7), applicationPrivilegeNames, applicationResouceNames);
+ final List indicesName = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5)));
+ final List indicesPrivilegeName = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5)));
+ final List indicesPrivilegeGrantedName = Arrays.asList(randomArray(3, String[]::new, () -> randomAlphaOfLength(5)));
+ final List indicesPrivilegeDeniedName = Arrays.asList(randomArray(3, String[]::new, () -> randomAlphaOfLength(5)));
+ final String indicesPrivilegeQuery = randomAlphaOfLengthBetween(0, 7);
+ final IndicesPrivileges indicesPrivilege = IndicesPrivileges.builder().indices(indicesName).privileges(indicesPrivilegeName)
+ .grantedFields(indicesPrivilegeGrantedName).deniedFields(indicesPrivilegeDeniedName).query(indicesPrivilegeQuery).build();
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ final Map expectedParams;
+ if (refreshPolicy != RefreshPolicy.NONE) {
+ expectedParams = Collections.singletonMap("refresh", refreshPolicy.getValue());
+ } else {
+ expectedParams = Collections.emptyMap();
+ }
+ final Role role = Role.builder().name(roleName).clusterPrivileges(clusterPrivileges).indicesPrivileges(indicesPrivilege)
+ .applicationResourcePrivileges(applicationResourcePrivilege).runAsPrivilege(runAsPrivilege).metadata(metadata).build();
+ final PutRoleRequest putRoleRequest = new PutRoleRequest(role, refreshPolicy);
+ final Request request = SecurityRequestConverters.putRole(putRoleRequest);
+ assertEquals(HttpPut.METHOD_NAME, request.getMethod());
+ assertEquals("/_xpack/security/role/" + roleName, request.getEndpoint());
+ assertEquals(expectedParams, request.getParameters());
+ assertToXContentBody(putRoleRequest, request.getEntity());
+ }
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
index 480f7abb131e1..27d8e755e973d 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
@@ -19,15 +19,11 @@
package org.elasticsearch.client.documentation;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.entity.ContentType;
-import org.apache.http.nio.entity.NStringEntity;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.LatchedActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
-import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.security.AuthenticateResponse;
@@ -65,6 +61,8 @@
import org.elasticsearch.client.security.PutPrivilegesResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleMappingResponse;
+import org.elasticsearch.client.security.PutRoleRequest;
+import org.elasticsearch.client.security.PutRoleResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
@@ -76,9 +74,7 @@
import org.elasticsearch.client.security.user.privileges.Role;
import org.elasticsearch.client.security.user.privileges.ApplicationPrivilege;
import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
-import org.elasticsearch.common.Strings;
import org.elasticsearch.common.util.set.Sets;
-import org.elasticsearch.common.xcontent.XContentBuilder;
import org.hamcrest.Matchers;
import javax.crypto.SecretKeyFactory;
@@ -97,7 +93,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -1024,18 +1019,67 @@ public void onFailure(Exception e) {
}
}
- // TODO: move all calls to high-level REST client once APIs for adding new role exist
- private void addRole(String roleName) throws IOException {
- Request addRoleRequest = new Request(HttpPost.METHOD_NAME, "/_xpack/security/role/" + roleName);
- try (XContentBuilder builder = jsonBuilder()) {
- builder.startObject();
- {
- builder.array("cluster", "all");
- }
- builder.endObject();
- addRoleRequest.setEntity(new NStringEntity(Strings.toString(builder), ContentType.APPLICATION_JSON));
+ public void testPutRole() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ {
+ // tag::put-role-execute
+ final Role role = Role.builder()
+ .name("testPutRole")
+ .clusterPrivileges(randomSubsetOf(1, Role.ClusterPrivilegeName.ALL_ARRAY))
+ .build();
+ final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.NONE);
+ final PutRoleResponse response = client.security().putRole(request, RequestOptions.DEFAULT);
+ // end::put-role-execute
+ // tag::put-role-response
+ boolean isCreated = response.isCreated(); // <1>
+ // end::put-role-response
+ assertTrue(isCreated);
+ }
+
+ {
+ final Role role = Role.builder()
+ .name("testPutRole")
+ .clusterPrivileges(randomSubsetOf(1, Role.ClusterPrivilegeName.ALL_ARRAY))
+ .build();
+ final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.NONE);
+ // tag::put-role-execute-listener
+ ActionListener listener = new ActionListener() {
+ @Override
+ public void onResponse(PutRoleResponse response) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::put-role-execute-listener
+
+ // Avoid unused variable warning
+ assertNotNull(listener);
+
+ // Replace the empty listener by a blocking listener in test
+ final PlainActionFuture future = new PlainActionFuture<>();
+ listener = future;
+
+ // tag::put-role-execute-async
+ client.security().putRoleAsync(request, RequestOptions.DEFAULT, listener); // <1>
+ // end::put-role-execute-async
+
+ assertNotNull(future.get(30, TimeUnit.SECONDS));
+ assertThat(future.get().isCreated(), is(false)); // false because it has already been created by the sync variant
}
- client().performRequest(addRoleRequest);
+ }
+
+ private void addRole(String roleName) throws IOException {
+ final Role role = Role.builder()
+ .name(roleName)
+ .clusterPrivileges("all")
+ .build();
+ final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.IMMEDIATE);
+ highLevelClient().security().putRole(request, RequestOptions.DEFAULT);
}
public void testCreateToken() throws Exception {
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java
index 41de52a8cef75..224b4d59d250d 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java
@@ -73,6 +73,7 @@ public void usedDeprecatedField(String usedName, String replacedWith) {
}
}, json)));
assertThat(response.getRoles().size(), equalTo(1));
+ assertThat(response.getTransientMetadataMap().size(), equalTo(1));
final Role role = response.getRoles().get(0);
assertThat(role.getName(), equalTo("my_admin_role"));
assertThat(role.getClusterPrivileges().size(), equalTo(1));
@@ -86,19 +87,20 @@ public void usedDeprecatedField(String usedName, String replacedWith) {
expectedMetadata.put("version", 1);
final Map expectedTransientMetadata = new HashMap<>();
expectedTransientMetadata.put("enabled", true);
+ assertThat(response.getTransientMetadataMap().get(role.getName()), equalTo(expectedTransientMetadata));
final Role expectedRole = Role.builder()
.name("my_admin_role")
.clusterPrivileges("all")
.indicesPrivileges(expectedIndicesPrivileges)
.runAsPrivilege("other_user")
.metadata(expectedMetadata)
- .transientMetadata(expectedTransientMetadata)
.build();
assertThat(role, equalTo(expectedRole));
}
public void testEqualsHashCode() {
final List roles = new ArrayList<>();
+ final Map> transientMetadataMap = new HashMap<>();
IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
.indices("index1", "index2")
.privileges("write", "monitor", "delete")
@@ -107,17 +109,17 @@ public void testEqualsHashCode() {
.build();
Map metadata = new HashMap<>();
metadata.put("key", "value");
- Map transientMetadata = new HashMap<>();
- transientMetadata.put("transient_key", "transient_value");
final Role role = Role.builder()
.name("role_name")
.clusterPrivileges("monitor", "manage", "manage_saml")
.indicesPrivileges(indicesPrivileges)
.runAsPrivilege("run_as_user")
.metadata(metadata)
- .transientMetadata(transientMetadata)
.build();
roles.add(role);
+ Map transientMetadata = new HashMap<>();
+ transientMetadata.put("transient_key", "transient_value");
+ transientMetadataMap.put(role.getName(), transientMetadata);
IndicesPrivileges indicesPrivileges2 = new IndicesPrivileges.Builder()
.indices("other_index1", "other_index2")
.privileges("write", "monitor", "delete")
@@ -126,31 +128,31 @@ public void testEqualsHashCode() {
.build();
Map metadata2 = new HashMap<>();
metadata.put("other_key", "other_value");
- Map transientMetadata2 = new HashMap<>();
- transientMetadata2.put("other_transient_key", "other_transient_value");
final Role role2 = Role.builder()
.name("role2_name")
.clusterPrivileges("monitor", "manage", "manage_saml")
.indicesPrivileges(indicesPrivileges2)
.runAsPrivilege("other_run_as_user")
.metadata(metadata2)
- .transientMetadata(transientMetadata2)
.build();
roles.add(role2);
- final GetRolesResponse getRolesResponse = new GetRolesResponse(roles);
- assertNotNull(getRolesResponse);
+ Map transientMetadata2 = new HashMap<>();
+ transientMetadata2.put("other_transient_key", "other_transient_value");
+ transientMetadataMap.put(role2.getName(), transientMetadata);
+ final GetRolesResponse getRolesResponse = new GetRolesResponse(roles, transientMetadataMap);
EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> {
- return new GetRolesResponse(original.getRoles());
+ return new GetRolesResponse(original.getRoles(), original.getTransientMetadataMap());
});
EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> {
- return new GetRolesResponse(original.getRoles());
+ return new GetRolesResponse(original.getRoles(), original.getTransientMetadataMap());
}, GetRolesResponseTests::mutateTestItem);
}
private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
+ final List roles = new ArrayList<>();
+ final Map> transientMetadataMap = new HashMap<>();
if (randomBoolean()) {
- final List roles = new ArrayList<>();
IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
.indices("index1", "index2")
.privileges("write", "monitor", "delete")
@@ -159,18 +161,18 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
.build();
Map metadata = new HashMap();
metadata.put("key", "value");
- Map transientMetadata = new HashMap<>();
- transientMetadata.put("transient_key", "transient_value");
final Role role = Role.builder()
.name("role_name")
.clusterPrivileges("monitor", "manage", "manage_saml")
.indicesPrivileges(indicesPrivileges)
.runAsPrivilege("run_as_user")
.metadata(metadata)
- .transientMetadata(transientMetadata)
.build();
roles.add(role);
- return new GetRolesResponse(roles);
+ Map transientMetadata = new HashMap<>();
+ transientMetadata.put("transient_key", "transient_value");
+ transientMetadataMap.put(role.getName(), transientMetadata);
+ return new GetRolesResponse(roles, transientMetadataMap);
} else {
IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder()
.indices("index1_changed", "index2")
@@ -180,20 +182,20 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) {
.build();
Map metadata = new HashMap();
metadata.put("key", "value");
- Map transientMetadata = new HashMap<>();
- transientMetadata.put("transient_key", "transient_value");
final Role role = Role.builder()
.name("role_name")
.clusterPrivileges("monitor", "manage", "manage_saml")
.indicesPrivileges(indicesPrivileges)
.runAsPrivilege("run_as_user")
.metadata(metadata)
- .transientMetadata(transientMetadata)
.build();
List newRoles = original.getRoles().stream().collect(Collectors.toList());
newRoles.remove(0);
newRoles.add(role);
- return new GetRolesResponse(newRoles);
+ Map transientMetadata = new HashMap<>();
+ transientMetadata.put("transient_key", "transient_value");
+ transientMetadataMap.put(role.getName(), transientMetadata);
+ return new GetRolesResponse(newRoles, transientMetadataMap);
}
}
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java
new file mode 100644
index 0000000000000..32860a807ae67
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
+import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.GlobalOperationPrivilege;
+import org.elasticsearch.client.security.user.privileges.GlobalPrivileges;
+import org.elasticsearch.client.security.user.privileges.GlobalPrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
+import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests;
+import org.elasticsearch.client.security.user.privileges.Role;
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.empty;
+
+public class PutRoleRequestTests extends AbstractXContentTestCase {
+
+ private static final String roleName = "testRoleName";
+
+ @Override
+ protected PutRoleRequest createTestInstance() {
+ final Role role = randomRole(roleName);
+ return new PutRoleRequest(role, null);
+ }
+
+ @Override
+ protected PutRoleRequest doParseInstance(XContentParser parser) throws IOException {
+ final Tuple> roleAndTransientMetadata = Role.fromXContent(parser, roleName);
+ assertThat(roleAndTransientMetadata.v2().entrySet(), is(empty()));
+ return new PutRoleRequest(roleAndTransientMetadata.v1(), null);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return false;
+ }
+
+ private static Role randomRole(String roleName) {
+ final Role.Builder roleBuilder = Role.builder().name(roleName)
+ .clusterPrivileges(randomSubsetOf(randomInt(3), Role.ClusterPrivilegeName.ALL_ARRAY))
+ .indicesPrivileges(
+ randomArray(3, IndicesPrivileges[]::new, () -> IndicesPrivilegesTests.createNewRandom(randomAlphaOfLength(3))))
+ .applicationResourcePrivileges(randomArray(3, ApplicationResourcePrivileges[]::new,
+ () -> ApplicationResourcePrivilegesTests.createNewRandom(randomAlphaOfLength(3).toLowerCase(Locale.ROOT))))
+ .runAsPrivilege(randomArray(3, String[]::new, () -> randomAlphaOfLength(3)));
+ if (randomBoolean()) {
+ roleBuilder.globalApplicationPrivileges(new GlobalPrivileges(Arrays.asList(
+ randomArray(1, 3, GlobalOperationPrivilege[]::new, () -> GlobalPrivilegesTests.buildRandomGlobalScopedPrivilege()))));
+ }
+ if (randomBoolean()) {
+ final Map metadata = new HashMap<>();
+ for (int i = 0; i < randomInt(3); i++) {
+ metadata.put(randomAlphaOfLength(3), randomAlphaOfLength(3));
+ }
+ roleBuilder.metadata(metadata);
+ }
+ return roleBuilder.build();
+ }
+
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java
index 9575363a40963..29845441a2c05 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java
@@ -31,13 +31,17 @@
public class ApplicationResourcePrivilegesTests extends AbstractXContentTestCase {
- @Override
- protected ApplicationResourcePrivileges createTestInstance() {
- return new ApplicationResourcePrivileges(randomAlphaOfLengthBetween(1, 8),
+ public static ApplicationResourcePrivileges createNewRandom(String name) {
+ return new ApplicationResourcePrivileges(name,
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8))),
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8))));
}
+ @Override
+ protected ApplicationResourcePrivileges createTestInstance() {
+ return createNewRandom(randomAlphaOfLengthBetween(1, 8));
+ }
+
@Override
protected ApplicationResourcePrivileges doParseInstance(XContentParser parser) throws IOException {
return ApplicationResourcePrivileges.fromXContent(parser);
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java
index a9d2702970962..927372c839e7c 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java
@@ -36,6 +36,25 @@ public class GlobalPrivilegesTests extends AbstractXContentTestCase privilege = new HashMap<>();
+ privilege.put("applications", Arrays.asList(generateRandomStringArray(4, 4, false)));
+ final GlobalOperationPrivilege priv = new GlobalOperationPrivilege("application", "manage", privilege);
+ return new GlobalPrivileges(Arrays.asList(priv));
+ }
+
+ public static GlobalOperationPrivilege buildRandomGlobalScopedPrivilege() {
+ final Map privilege = new HashMap<>();
+ for (int i = 0; i < randomIntBetween(1, 4); i++) {
+ if (randomBoolean()) {
+ privilege.put(randomAlphaOfLength(2) + idCounter++, randomAlphaOfLengthBetween(0, 4));
+ } else {
+ privilege.put(randomAlphaOfLength(2) + idCounter++, Arrays.asList(generateRandomStringArray(4, 4, false)));
+ }
+ }
+ return new GlobalOperationPrivilege(randomFrom(GlobalPrivileges.CATEGORIES), randomAlphaOfLength(2) + idCounter++, privilege);
+ }
+
@Override
protected GlobalPrivileges createTestInstance() {
final List privilegeList = Arrays
@@ -85,14 +104,6 @@ public void testSameScopeGlobalOperationPrivilege() {
assertThat(e.getMessage(), is("Different privileges for the same category and operation are not permitted"));
}
- private static GlobalOperationPrivilege buildRandomGlobalScopedPrivilege() {
- final Map privilege = new HashMap<>();
- for (int i = 0; i < randomIntBetween(1, 4); i++) {
- privilege.put(randomAlphaOfLength(2) + idCounter++, randomAlphaOfLengthBetween(1, 4));
- }
- return new GlobalOperationPrivilege("application", randomAlphaOfLength(2) + idCounter++, privilege);
- }
-
public void testEqualsHashCode() {
final List privilegeList = Arrays
.asList(randomArray(1, 4, size -> new GlobalOperationPrivilege[size], () -> buildRandomGlobalScopedPrivilege()));
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java
new file mode 100644
index 0000000000000..41442d2a83687
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security.user.privileges;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+public class IndicesPrivilegesTests extends AbstractXContentTestCase {
+
+ public static IndicesPrivileges createNewRandom(String query) {
+ final IndicesPrivileges.Builder indicesPrivilegesBuilder = IndicesPrivileges.builder()
+ .indices(generateRandomStringArray(4, 4, false, false))
+ .privileges(randomSubsetOf(randomIntBetween(1, 4), Role.IndexPrivilegeName.ALL_ARRAY))
+ .query(query);
+ if (randomBoolean()) {
+ final List fields = Arrays.asList(generateRandomStringArray(4, 4, false));
+ indicesPrivilegesBuilder.grantedFields(fields);
+ if (randomBoolean()) {
+ indicesPrivilegesBuilder.deniedFields(randomSubsetOf(fields));
+ }
+ }
+ return indicesPrivilegesBuilder.build();
+ }
+
+ @Override
+ protected IndicesPrivileges createTestInstance() {
+ return createNewRandom(
+ randomBoolean() ? null : "{ " + randomAlphaOfLengthBetween(1, 4) + " : " + randomAlphaOfLengthBetween(1, 4) + " }");
+ }
+
+ @Override
+ protected IndicesPrivileges doParseInstance(XContentParser parser) throws IOException {
+ return IndicesPrivileges.fromXContent(parser);
+ }
+
+ @Override
+ protected boolean supportsUnknownFields() {
+ return false;
+ }
+}
diff --git a/docs/java-rest/high-level/security/put-role.asciidoc b/docs/java-rest/high-level/security/put-role.asciidoc
new file mode 100644
index 0000000000000..68c1f5d69d470
--- /dev/null
+++ b/docs/java-rest/high-level/security/put-role.asciidoc
@@ -0,0 +1,37 @@
+
+--
+:api: put-role
+:request: PutRoleRequest
+:response: PutRoleResponse
+--
+
+[id="{upid}-{api}"]
+=== Put Role API
+
+[id="{upid}-{api}-request"]
+==== Put Role Request
+
+The +{request}+ class is used to create or update a role in the Native Roles
+Store. The request contains a single role, which encapsulates privileges over
+resources. A role can be assigned to an user using the
+<<{upid}-put-role-mapping, Put Role Mapping API>>.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Put Role Response
+
+The returned +{response}+ contains a single field, `created`. This field
+serves as an indication if the role was created or if an existing entry was
+updated.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> `created` is a boolean indicating whether the role was created or updated
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index a25205d9533ff..82b868b59d6b3 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -385,6 +385,7 @@ The Java High Level REST Client supports the following Security APIs:
* <>
* <>
* <>
+* <<{upid}-put-role>>
* <<{upid}-get-roles>>
* <>
* <<{upid}-clear-roles-cache>>
@@ -406,6 +407,7 @@ include::security/delete-user.asciidoc[]
include::security/enable-user.asciidoc[]
include::security/disable-user.asciidoc[]
include::security/change-password.asciidoc[]
+include::security/put-role.asciidoc[]
include::security/get-roles.asciidoc[]
include::security/delete-role.asciidoc[]
include::security/delete-privileges.asciidoc[]