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 a033ee61f79dc..811b9c9053ee8 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
@@ -52,6 +52,8 @@
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
+import org.elasticsearch.client.security.PutPrivilegesRequest;
+import org.elasticsearch.client.security.PutPrivilegesResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleMappingResponse;
import org.elasticsearch.client.security.PutUserRequest;
@@ -603,6 +605,38 @@ public void getPrivilegesAsync(final GetPrivilegesRequest request, final Request
options, GetPrivilegesResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Create or update application privileges.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to create or update application privileges
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the create or update application privileges call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public PutPrivilegesResponse putPrivileges(final PutPrivilegesRequest request, final RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putPrivileges, options,
+ PutPrivilegesResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously create or update application privileges.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to create or update application privileges
+ * @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 putPrivilegesAsync(final PutPrivilegesRequest request, final RequestOptions options,
+ final ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putPrivileges, options,
+ PutPrivilegesResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Removes application privilege(s)
* 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 6485899acf947..d893c20c3b2fd 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
@@ -28,17 +28,18 @@
import org.elasticsearch.client.security.ClearRolesCacheRequest;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
-import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
import org.elasticsearch.client.security.DeleteUserRequest;
-import org.elasticsearch.client.security.InvalidateTokenRequest;
-import org.elasticsearch.client.security.GetRolesRequest;
-import org.elasticsearch.client.security.PutRoleMappingRequest;
-import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
+import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
+import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.HasPrivilegesRequest;
+import org.elasticsearch.client.security.InvalidateTokenRequest;
+import org.elasticsearch.client.security.PutPrivilegesRequest;
+import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.SetUserEnabledRequest;
import org.elasticsearch.common.Strings;
@@ -213,6 +214,14 @@ static Request getPrivileges(GetPrivilegesRequest getPrivilegesRequest) {
return new Request(HttpGet.METHOD_NAME, endpoint);
}
+ static Request putPrivileges(final PutPrivilegesRequest putPrivilegesRequest) throws IOException {
+ Request request = new Request(HttpPut.METHOD_NAME, "/_xpack/security/privilege");
+ request.setEntity(createEntity(putPrivilegesRequest, REQUEST_BODY_CONTENT_TYPE));
+ RequestConverters.Params params = new RequestConverters.Params(request);
+ params.withRefreshPolicy(putPrivilegesRequest.getRefreshPolicy());
+ return request;
+ }
+
static Request deletePrivileges(DeletePrivilegesRequest deletePrivilegeRequest) {
String endpoint = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack/security/privilege")
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesRequest.java
new file mode 100644
index 0000000000000..3e20405046a0c
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesRequest.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.ApplicationPrivilege;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+/**
+ * Request object for creating/updating application privileges.
+ */
+public final class PutPrivilegesRequest implements Validatable, ToXContentObject {
+
+ private final Map> privileges;
+ private final RefreshPolicy refreshPolicy;
+
+ public PutPrivilegesRequest(final List privileges, @Nullable final RefreshPolicy refreshPolicy) {
+ if (privileges == null || privileges.isEmpty()) {
+ throw new IllegalArgumentException("privileges are required");
+ }
+ this.privileges = Collections.unmodifiableMap(privileges.stream()
+ .collect(Collectors.groupingBy(ApplicationPrivilege::getApplication, TreeMap::new, Collectors.toList())));
+ this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.IMMEDIATE : refreshPolicy;
+ }
+
+ /**
+ * @return a map of application name to list of
+ * {@link ApplicationPrivilege}s
+ */
+ public Map> getPrivileges() {
+ return privileges;
+ }
+
+ public RefreshPolicy getRefreshPolicy() {
+ return refreshPolicy;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(privileges, refreshPolicy);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || (this.getClass() != o.getClass())) {
+ return false;
+ }
+ final PutPrivilegesRequest that = (PutPrivilegesRequest) o;
+ return privileges.equals(that.privileges) && (refreshPolicy == that.refreshPolicy);
+ }
+
+ @Override
+ public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
+ builder.startObject();
+ for (Entry> entry : privileges.entrySet()) {
+ builder.field(entry.getKey());
+ builder.startObject();
+ for (ApplicationPrivilege applicationPrivilege : entry.getValue()) {
+ builder.field(applicationPrivilege.getName());
+ applicationPrivilege.toXContent(builder, params);
+ }
+ builder.endObject();
+ }
+ return builder.endObject();
+ }
+
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesResponse.java
new file mode 100644
index 0000000000000..fadf6d155dc69
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesResponse.java
@@ -0,0 +1,105 @@
+/*
+ * 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.ParsingException;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Response when creating/updating one or more application privileges to the
+ * security index.
+ */
+public final class PutPrivilegesResponse {
+
+ /*
+ * Map of application name to a map of privilege name to boolean denoting
+ * created or update status.
+ */
+ private final Map> applicationPrivilegesCreatedOrUpdated;
+
+ public PutPrivilegesResponse(final Map> applicationPrivilegesCreatedOrUpdated) {
+ this.applicationPrivilegesCreatedOrUpdated = Collections.unmodifiableMap(applicationPrivilegesCreatedOrUpdated);
+ }
+
+ /**
+ * Get response status for the request to create or update application
+ * privileges.
+ *
+ * @param applicationName application name as specified in the request
+ * @param privilegeName privilege name as specified in the request
+ * @return {@code true} if the privilege was created, {@code false} if the
+ * privilege was updated
+ * @throws IllegalArgumentException thrown for unknown application name or
+ * privilege name.
+ */
+ public boolean wasCreated(final String applicationName, final String privilegeName) {
+ if (Strings.hasText(applicationName) == false) {
+ throw new IllegalArgumentException("application name is required");
+ }
+ if (Strings.hasText(privilegeName) == false) {
+ throw new IllegalArgumentException("privilege name is required");
+ }
+ if (applicationPrivilegesCreatedOrUpdated.get(applicationName) == null
+ || applicationPrivilegesCreatedOrUpdated.get(applicationName).get(privilegeName) == null) {
+ throw new IllegalArgumentException("application name or privilege name not found in the response");
+ }
+ return applicationPrivilegesCreatedOrUpdated.get(applicationName).get(privilegeName);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static PutPrivilegesResponse fromXContent(final XContentParser parser) throws IOException {
+ final Map> applicationPrivilegesCreatedOrUpdated = new HashMap<>();
+ XContentParser.Token token = parser.currentToken();
+ if (token == null) {
+ token = parser.nextToken();
+ }
+ final Map appNameToPrivStatus = parser.map();
+ for (Entry entry : appNameToPrivStatus.entrySet()) {
+ if (entry.getValue() instanceof Map) {
+ final Map privilegeToStatus = applicationPrivilegesCreatedOrUpdated.computeIfAbsent(entry.getKey(),
+ (a) -> new HashMap<>());
+ final Map createdOrUpdated = (Map) entry.getValue();
+ for (String privilegeName : createdOrUpdated.keySet()) {
+ if (createdOrUpdated.get(privilegeName) instanceof Map) {
+ final Map statusMap = (Map) createdOrUpdated.get(privilegeName);
+ final Object status = statusMap.get("created");
+ if (status instanceof Boolean) {
+ privilegeToStatus.put(privilegeName, (Boolean) status);
+ } else {
+ throw new ParsingException(parser.getTokenLocation(), "Failed to parse object, unexpected structure");
+ }
+ } else {
+ throw new ParsingException(parser.getTokenLocation(), "Failed to parse object, unexpected structure");
+ }
+ }
+ } else {
+ throw new ParsingException(parser.getTokenLocation(), "Failed to parse object, unexpected structure");
+ }
+ }
+ return new PutPrivilegesResponse(applicationPrivilegesCreatedOrUpdated);
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java
index 1f5a4f08191cc..fc21dfc0b2816 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java
@@ -24,6 +24,8 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
@@ -44,7 +46,7 @@
* actions and metadata are completely managed by the client and can contain arbitrary
* string values.
*/
-public final class ApplicationPrivilege {
+public final class ApplicationPrivilege implements ToXContentObject {
private static final ParseField APPLICATION = new ParseField("application");
private static final ParseField NAME = new ParseField("name");
@@ -171,4 +173,16 @@ public ApplicationPrivilege build() {
}
}
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject()
+ .field(APPLICATION.getPreferredName(), application)
+ .field(NAME.getPreferredName(), name)
+ .field(ACTIONS.getPreferredName(), actions);
+ if (metadata != null && metadata.isEmpty() == false) {
+ builder.field(METADATA.getPreferredName(), metadata);
+ }
+ return builder.endObject();
+ }
+
}
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 110e0cc56c986..f5015c1203dea 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
@@ -19,10 +19,11 @@
package org.elasticsearch.client;
-import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
+import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
@@ -32,8 +33,8 @@
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
-import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.GetRolesRequest;
+import org.elasticsearch.client.security.PutPrivilegesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.RefreshPolicy;
@@ -41,10 +42,13 @@
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.ApplicationPrivilege;
import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -318,6 +322,27 @@ public void testGetAllPrivileges() throws Exception {
assertNull(request.getEntity());
}
+ public void testPutPrivileges() throws Exception {
+ int noOfApplicationPrivileges = randomIntBetween(2, 4);
+ final List privileges = new ArrayList<>();
+ for (int count = 0; count < noOfApplicationPrivileges; count++) {
+ privileges.add(ApplicationPrivilege.builder()
+ .application(randomAlphaOfLength(4))
+ .privilege(randomAlphaOfLengthBetween(3, 5))
+ .actions(Sets.newHashSet(generateRandomStringArray(3, 5, false, false)))
+ .metadata(Collections.singletonMap("k1", "v1"))
+ .build());
+ }
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ final Map expectedParams = getExpectedParamsFromRefreshPolicy(refreshPolicy);
+ final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy);
+ final Request request = SecurityRequestConverters.putPrivileges(putPrivilegesRequest);
+ assertEquals(HttpPut.METHOD_NAME, request.getMethod());
+ assertEquals("/_xpack/security/privilege", request.getEndpoint());
+ assertEquals(expectedParams, request.getParameters());
+ assertToXContentBody(putPrivilegesRequest, request.getEntity());
+ }
+
public void testDeletePrivileges() {
final String application = randomAlphaOfLengthBetween(1, 12);
final List privileges = randomSubsetOf(randomIntBetween(1, 3), "read", "write", "all");
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 79258b314510c..480f7abb131e1 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
@@ -29,7 +29,6 @@
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.ChangePasswordRequest;
@@ -62,6 +61,8 @@
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
+import org.elasticsearch.client.security.PutPrivilegesRequest;
+import org.elasticsearch.client.security.PutPrivilegesResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleMappingResponse;
import org.elasticsearch.client.security.PutUserRequest;
@@ -78,7 +79,6 @@
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.rest.RestStatus;
import org.hamcrest.Matchers;
import javax.crypto.SecretKeyFactory;
@@ -86,6 +86,7 @@
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -1205,36 +1206,23 @@ public void testGetPrivileges() throws Exception {
new ApplicationPrivilege("testapp2", "all", Arrays.asList("action:login", "data:write/*", "manage:*"), null);
{
- //TODO Replace this with a call to PutPrivileges once it is implemented
- final Request createPrivilegeRequest = new Request("POST", "/_xpack/security/privilege");
- createPrivilegeRequest.setJsonEntity("{" +
- " \"testapp\": {" +
- " \"read\": {" +
- " \"actions\": [ \"action:login\", \"data:read/*\" ]" +
- " }," +
- " \"write\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" ]," +
- " \"metadata\": { \"key1\": \"value1\" }" +
- " }," +
- " \"all\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" , \"manage:*\"]" +
- " }" +
- " }," +
- " \"testapp2\": {" +
- " \"read\": {" +
- " \"actions\": [ \"action:login\", \"data:read/*\" ]," +
- " \"metadata\": { \"key2\": \"value2\" }" +
- " }," +
- " \"write\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" ]" +
- " }," +
- " \"all\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" , \"manage:*\"]" +
- " }" +
- " }" +
- "}");
- final Response createPrivilegeResponse = client.getLowLevelClient().performRequest(createPrivilegeRequest);
- assertEquals(RestStatus.OK.getStatus(), createPrivilegeResponse.getStatusLine().getStatusCode());
+ List applicationPrivileges = new ArrayList<>();
+ applicationPrivileges.add(readTestappPrivilege);
+ applicationPrivileges.add(writeTestappPrivilege);
+ applicationPrivileges.add(allTestappPrivilege);
+ applicationPrivileges.add(readTestapp2Privilege);
+ applicationPrivileges.add(writeTestapp2Privilege);
+ applicationPrivileges.add(allTestapp2Privilege);
+ PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(applicationPrivileges, RefreshPolicy.IMMEDIATE);
+ PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest, RequestOptions.DEFAULT);
+
+ assertNotNull(putPrivilegesResponse);
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "write"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "read"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "all"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp2", "all"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp2", "write"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp2", "read"), is(true));
}
{
@@ -1327,26 +1315,105 @@ public void onFailure(Exception e) {
}
}
+ public void testPutPrivileges() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ {
+ // tag::put-privileges-request
+ final List privileges = new ArrayList<>();
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("all")
+ .actions(Sets.newHashSet("action:login"))
+ .metadata(Collections.singletonMap("k1", "v1"))
+ .build());
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("write")
+ .actions(Sets.newHashSet("action:write"))
+ .build());
+ final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, RefreshPolicy.IMMEDIATE);
+ // end::put-privileges-request
+
+ // tag::put-privileges-execute
+ final PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest,
+ RequestOptions.DEFAULT);
+ // end::put-privileges-execute
+
+ final String applicationName = "app01";
+ final String privilegeName = "all";
+ // tag::put-privileges-response
+ final boolean status = putPrivilegesResponse.wasCreated(applicationName, privilegeName); // <1>
+ // end::put-privileges-response
+ assertThat(status, is(true));
+ }
+
+ {
+ final List privileges = new ArrayList<>();
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("all")
+ .actions(Sets.newHashSet("action:login"))
+ .metadata(Collections.singletonMap("k1", "v1"))
+ .build());
+ final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, RefreshPolicy.IMMEDIATE);
+
+ // tag::put-privileges-execute-listener
+ ActionListener listener = new ActionListener() {
+ @Override
+ public void onResponse(PutPrivilegesResponse response) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::put-privileges-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-privileges-execute-async
+ client.security().putPrivilegesAsync(putPrivilegesRequest, RequestOptions.DEFAULT, listener); // <1>
+ //end::put-privileges-execute-async
+
+ assertNotNull(future.get(30, TimeUnit.SECONDS));
+ assertThat(future.get().wasCreated("app01", "all"), is(false));
+ }
+ }
+
public void testDeletePrivilege() throws Exception {
RestHighLevelClient client = highLevelClient();
{
- final Request createPrivilegeRequest = new Request("POST", "/_xpack/security/privilege");
- createPrivilegeRequest.setJsonEntity("{" +
- " \"testapp\": {" +
- " \"read\": {" +
- " \"actions\": [ \"action:login\", \"data:read/*\" ]" +
- " }," +
- " \"write\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" ]" +
- " }," +
- " \"all\": {" +
- " \"actions\": [ \"action:login\", \"data:write/*\" ]" +
- " }" +
- " }" +
- "}");
-
- final Response createPrivilegeResponse = client.getLowLevelClient().performRequest(createPrivilegeRequest);
- assertEquals(RestStatus.OK.getStatus(), createPrivilegeResponse.getStatusLine().getStatusCode());
+ List applicationPrivileges = new ArrayList<>();
+ applicationPrivileges.add(ApplicationPrivilege.builder()
+ .application("testapp")
+ .privilege("read")
+ .actions("action:login", "data:read/*")
+ .build());
+ applicationPrivileges.add(ApplicationPrivilege.builder()
+ .application("testapp")
+ .privilege("write")
+ .actions("action:login", "data:write/*")
+ .build());
+ applicationPrivileges.add(ApplicationPrivilege.builder()
+ .application("testapp")
+ .privilege("all")
+ .actions("action:login", "data:write/*")
+ .build());
+ PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(applicationPrivileges, RefreshPolicy.IMMEDIATE);
+ PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest, RequestOptions.DEFAULT);
+
+ assertNotNull(putPrivilegesResponse);
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "write"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "read"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("testapp", "all"), is(true));
}
{
// tag::delete-privileges-request
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesRequestTests.java
new file mode 100644
index 0000000000000..8c60382eeec38
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesRequestTests.java
@@ -0,0 +1,170 @@
+/*
+ * 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.ApplicationPrivilege;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.util.set.Sets;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class PutPrivilegesRequestTests extends ESTestCase {
+
+ public void testConstructor() {
+ final List privileges = randomFrom(
+ Arrays.asList(Collections.singletonList(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("all")
+ .actions(Sets.newHashSet("action:login", "action:logout"))
+ .metadata(Collections.singletonMap("k1", "v1"))
+ .build()),
+ null, Collections.emptyList()));
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ if (privileges == null || privileges.isEmpty()) {
+ final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class,
+ () -> new PutPrivilegesRequest(privileges, refreshPolicy));
+ assertThat(ile.getMessage(), equalTo("privileges are required"));
+ } else {
+ final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy);
+ assertThat(putPrivilegesRequest.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()),
+ equalTo(privileges));
+ assertThat(putPrivilegesRequest.getRefreshPolicy(), equalTo(refreshPolicy));
+ }
+ }
+
+ public void testToXContent() throws IOException {
+ final String expected = "{\n"
+ + " \"app01\" : {\n"
+ + " \"all\" : {\n"
+ + " \"application\" : \"app01\",\n"
+ + " \"name\" : \"all\",\n"
+ + " \"actions\" : [\n"
+ + " \"action:logout\",\n"
+ + " \"action:login\"\n"
+ + " ],\n"
+ + " \"metadata\" : {\n"
+ + " \"k1\" : \"v1\"\n"
+ + " }\n"
+ + " },\n"
+ + " \"read\" : {\n"
+ + " \"application\" : \"app01\",\n"
+ + " \"name\" : \"read\",\n"
+ + " \"actions\" : [\n"
+ + " \"data:read\"\n"
+ + " ]\n" + " }\n"
+ + " },\n"
+ + " \"app02\" : {\n"
+ + " \"all\" : {\n"
+ + " \"application\" : \"app02\",\n"
+ + " \"name\" : \"all\",\n"
+ + " \"actions\" : [\n"
+ + " \"action:logout\",\n"
+ + " \"action:login\"\n"
+ + " ],\n"
+ + " \"metadata\" : {\n"
+ + " \"k2\" : \"v2\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n"
+ + "}";
+ List privileges = new ArrayList<>();
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("all")
+ .actions(Sets.newHashSet("action:login", "action:logout"))
+ .metadata(Collections.singletonMap("k1", "v1"))
+ .build());
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app01")
+ .privilege("read")
+ .actions(Sets.newHashSet("data:read"))
+ .build());
+ privileges.add(ApplicationPrivilege.builder()
+ .application("app02")
+ .privilege("all")
+ .actions(Sets.newHashSet("action:login", "action:logout"))
+ .metadata(Collections.singletonMap("k2", "v2"))
+ .build());
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy);
+ final XContentBuilder builder = XContentFactory.jsonBuilder().prettyPrint();
+ assertThat(Strings.toString(putPrivilegesRequest.toXContent(builder, ToXContent.EMPTY_PARAMS)), equalTo(expected));
+ }
+
+ public void testEqualsHashCode() {
+ final List privileges = new ArrayList<>();
+ privileges.add(ApplicationPrivilege.builder()
+ .application(randomAlphaOfLength(5))
+ .privilege(randomAlphaOfLength(3))
+ .actions(Sets.newHashSet(randomAlphaOfLength(5), randomAlphaOfLength(5)))
+ .metadata(Collections.singletonMap(randomAlphaOfLength(3), randomAlphaOfLength(3)))
+ .build());
+ privileges.add(ApplicationPrivilege.builder()
+ .application(randomAlphaOfLength(5))
+ .privilege(randomAlphaOfLength(3))
+ .actions(Sets.newHashSet(randomAlphaOfLength(5), randomAlphaOfLength(5)))
+ .metadata(Collections.singletonMap(randomAlphaOfLength(3), randomAlphaOfLength(3)))
+ .build());
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy);
+
+ EqualsHashCodeTestUtils.checkEqualsAndHashCode(putPrivilegesRequest, (original) -> {
+ return new PutPrivilegesRequest(privileges, refreshPolicy);
+ });
+ EqualsHashCodeTestUtils.checkEqualsAndHashCode(putPrivilegesRequest, (original) -> {
+ return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()),
+ original.getRefreshPolicy());
+ }, PutPrivilegesRequestTests::mutateTestItem);
+ }
+
+ private static PutPrivilegesRequest mutateTestItem(PutPrivilegesRequest original) {
+ final Set policies = Sets.newHashSet(RefreshPolicy.values());
+ policies.remove(original.getRefreshPolicy());
+ switch (randomIntBetween(0, 1)) {
+ case 0:
+ final List privileges = new ArrayList<>();
+ privileges.add(ApplicationPrivilege.builder()
+ .application(randomAlphaOfLength(5))
+ .privilege(randomAlphaOfLength(3))
+ .actions(Sets.newHashSet(randomAlphaOfLength(6)))
+ .build());
+ return new PutPrivilegesRequest(privileges, original.getRefreshPolicy());
+ case 1:
+ return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()),
+ randomFrom(policies));
+ default:
+ return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()),
+ randomFrom(policies));
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java
new file mode 100644
index 0000000000000..9a39f456986e5
--- /dev/null
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java
@@ -0,0 +1,86 @@
+/*
+ * 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.xcontent.XContentType;
+import org.elasticsearch.test.ESTestCase;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+public class PutPrivilegesResponseTests extends ESTestCase {
+
+ public void testFromXContent() throws IOException {
+ final String json = "{\n" +
+ " \"app02\": {\n" +
+ " \"all\": {\n" +
+ " \"created\": true\n" +
+ " }\n" +
+ " },\n" +
+ " \"app01\": {\n" +
+ " \"read\": {\n" +
+ " \"created\": false\n" +
+ " },\n" +
+ " \"write\": {\n" +
+ " \"created\": true\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+
+ final PutPrivilegesResponse putPrivilegesResponse = PutPrivilegesResponse
+ .fromXContent(createParser(XContentType.JSON.xContent(), json));
+
+ assertThat(putPrivilegesResponse.wasCreated("app02", "all"), is(true));
+ assertThat(putPrivilegesResponse.wasCreated("app01", "read"), is(false));
+ assertThat(putPrivilegesResponse.wasCreated("app01", "write"), is(true));
+ expectThrows(IllegalArgumentException.class, () -> putPrivilegesResponse.wasCreated("unknown-app", "unknown-priv"));
+ expectThrows(IllegalArgumentException.class, () -> putPrivilegesResponse.wasCreated("app01", "unknown-priv"));
+ }
+
+ public void testGetStatusFailsForUnknownApplicationOrPrivilegeName() {
+ final PutPrivilegesResponse putPrivilegesResponse = new PutPrivilegesResponse(
+ Collections.singletonMap("app-1", Collections.singletonMap("priv", true)));
+
+ final boolean invalidAppName = randomBoolean();
+ final String applicationName = (invalidAppName) ? randomAlphaOfLength(4) : "app-1";
+ final String privilegeName = randomAlphaOfLength(4);
+
+ final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class,
+ () -> putPrivilegesResponse.wasCreated(applicationName, privilegeName));
+ assertThat(ile.getMessage(), equalTo("application name or privilege name not found in the response"));
+ }
+
+ public void testGetStatusFailsForNullOrEmptyApplicationOrPrivilegeName() {
+ final PutPrivilegesResponse putPrivilegesResponse = new PutPrivilegesResponse(
+ Collections.singletonMap("app-1", Collections.singletonMap("priv", true)));
+
+ final boolean nullOrEmptyAppName = randomBoolean();
+ final String applicationName = (nullOrEmptyAppName) ? randomFrom(Arrays.asList("", " ", null)) : "app-1";
+ final String privilegeName = randomFrom(Arrays.asList("", " ", null));
+ final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class,
+ () -> putPrivilegesResponse.wasCreated(applicationName, privilegeName));
+ assertThat(ile.getMessage(),
+ (nullOrEmptyAppName ? equalTo("application name is required") : equalTo("privilege name is required")));
+ }
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java
index f958cadaa7e80..b720187673023 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java
@@ -19,8 +19,12 @@
package org.elasticsearch.client.security.user.privileges;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
@@ -36,19 +40,19 @@
public class ApplicationPrivilegeTests extends ESTestCase {
- public void testFromXContent() throws IOException {
+ public void testFromXContentAndToXContent() throws IOException {
String json =
- " {" +
- " \"application\": \"myapp\"," +
- " \"name\": \"read\"," +
- " \"actions\": [" +
- " \"data:read/*\"," +
- " \"action:login\"" +
- " ],\n" +
- " \"metadata\": {" +
- " \"description\": \"Read access to myapp\"" +
- " }" +
- " }";
+ "{\n"
+ + " \"application\" : \"myapp\",\n"
+ + " \"name\" : \"read\",\n"
+ + " \"actions\" : [\n"
+ + " \"data:read/*\",\n"
+ + " \"action:login\"\n"
+ + " ],\n"
+ + " \"metadata\" : {\n"
+ + " \"description\" : \"Read access to myapp\"\n"
+ + " }\n"
+ + "}";
final ApplicationPrivilege privilege = ApplicationPrivilege.fromXContent(XContentType.JSON.xContent().createParser(
new NamedXContentRegistry(Collections.emptyList()), new DeprecationHandler() {
@Override
@@ -64,6 +68,10 @@ public void usedDeprecatedField(String usedName, String replacedWith) {
final ApplicationPrivilege expectedPrivilege =
new ApplicationPrivilege("myapp", "read", Arrays.asList("data:read/*", "action:login"), metadata);
assertThat(privilege, equalTo(expectedPrivilege));
+
+ XContentBuilder builder = privilege.toXContent(XContentFactory.jsonBuilder().prettyPrint(), ToXContent.EMPTY_PARAMS);
+ String toJson = Strings.toString(builder);
+ assertThat(toJson, equalTo(json));
}
public void testEmptyApplicationName() {
diff --git a/docs/java-rest/high-level/security/put-privileges.asciidoc b/docs/java-rest/high-level/security/put-privileges.asciidoc
new file mode 100644
index 0000000000000..1c0a97d2a94c3
--- /dev/null
+++ b/docs/java-rest/high-level/security/put-privileges.asciidoc
@@ -0,0 +1,39 @@
+--
+:api: put-privileges
+:request: PutPrivilegesRequest
+:response: PutPrivilegesResponse
+--
+
+[id="{upid}-{api}"]
+=== Put Privileges API
+
+Application privileges can be created or updated using this API.
+
+[id="{upid}-{api}-request"]
+==== Put Privileges Request
+A +{request}+ contains list of application privileges that
+need to be created or updated. Each application privilege
+consists of an application name, application privilege,
+set of actions and optional metadata.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Put Privileges Response
+
+The returned +{response}+ contains the information about the status
+for each privilege present in the +{request}+. The status would be
+`true` if the privilege was created, `false` if the privilege was updated.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
+<1> The response contains the status for given application name and
+privilege name. The status would be `true` if the privilege was created,
+`false` if the privilege was updated.
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index eb2f0b9818172..9fa7927f96937 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -390,6 +390,7 @@ The Java High Level REST Client supports the following Security APIs:
* <>
* <<{upid}-invalidate-token>>
* <<{upid}-get-privileges>>
+* <<{upid}-put-privileges>>
* <<{upid}-delete-privileges>>
include::security/put-user.asciidoc[]
@@ -411,6 +412,7 @@ include::security/get-role-mappings.asciidoc[]
include::security/delete-role-mapping.asciidoc[]
include::security/create-token.asciidoc[]
include::security/invalidate-token.asciidoc[]
+include::security/put-privileges.asciidoc[]
== Watcher APIs