From 914dc0beebe5721b4818147e48dff5f172a6d70b Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 14 Nov 2018 17:19:50 +1100 Subject: [PATCH 01/11] [HLRC] Add support for put privileges API This commit adds support for API to create or update application privileges. Note: I have used the ApplicationPrivilege and Tests from the changes done for get privileges API (by Ioannis) and have modified to support toXContent. --- .../elasticsearch/client/SecurityClient.java | 33 ++++ .../client/SecurityRequestConverters.java | 11 +- .../client/security/PutPrivilegesRequest.java | 87 ++++++++ .../security/PutPrivilegesResponse.java | 103 ++++++++++ .../user/privileges/ApplicationPrivilege.java | 186 ++++++++++++++++++ .../SecurityRequestConvertersTests.java | 29 ++- .../SecurityDocumentationIT.java | 73 +++++++ .../security/PutPrivilegesRequestTests.java | 124 ++++++++++++ .../security/PutPrivilegesResponseTests.java | 83 ++++++++ .../privileges/ApplicationPrivilegeTests.java | 118 +++++++++++ .../security/put-privileges.asciidoc | 40 ++++ .../high-level/supported-apis.asciidoc | 2 + 12 files changed, 886 insertions(+), 3 deletions(-) create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesRequest.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesResponse.java create mode 100644 client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesRequestTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java create mode 100644 client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java create mode 100644 docs/java-rest/high-level/security/put-privileges.asciidoc 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 93d29056a707a..2f9ebefbf4910 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 @@ -46,6 +46,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; @@ -505,6 +507,37 @@ public void invalidateTokenAsync(InvalidateTokenRequest request, RequestOptions InvalidateTokenResponse::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 216085af78a38..2f97ec1f69e17 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 @@ -30,11 +30,12 @@ import org.elasticsearch.client.security.DeletePrivilegesRequest; import org.elasticsearch.client.security.DeleteRoleMappingRequest; import org.elasticsearch.client.security.DeleteRoleRequest; -import org.elasticsearch.client.security.HasPrivilegesRequest; import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.GetRoleMappingsRequest; +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; @@ -181,6 +182,14 @@ static Request invalidateToken(InvalidateTokenRequest invalidateTokenRequest) th return request; } + 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..3b343a4d4ac20 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesRequest.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.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.Objects; + +/** + * Request object for creating/updating application privileges. + */ +public final class PutPrivilegesRequest implements Validatable, ToXContentObject { + + private final List 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.unmodifiableList(privileges); + this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.IMMEDIATE : refreshPolicy; + } + + public List 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 (ApplicationPrivilege privilege : privileges) { + builder.field(privilege.getApplication()); + builder.startObject(); + builder.field(privilege.getName()); + privilege.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..99e09d1bc6dfb --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutPrivilegesResponse.java @@ -0,0 +1,103 @@ +/* + * 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.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.Objects; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +/** + * Response when creating/updating one or more application privileges to the + * security index. + */ +public final class PutPrivilegesResponse { + public enum Status { + CREATED, UPDATED, UNKNOWN + } + + 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 {@link Status#CREATED} if the privilege was created, + * {@link Status#UPDATED} if the privilege was updated and for unknown + * application name / privilege name the status is {@link Status#UNKNOWN} + */ + public Status status(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"); + } + return applicationPrivilegesCreatedOrUpdated.getOrDefault(applicationName, Collections.emptyMap()) + .getOrDefault(privilegeName, Status.UNKNOWN); + } + + 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(); + } + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + final String application = parser.currentName(); + final Map privilegeToStatus = applicationPrivilegesCreatedOrUpdated.computeIfAbsent(application, + (a) -> new HashMap<>()); + + token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + final String privilege = parser.currentName(); + token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); + String currentFieldName = null; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if ("created".equals(currentFieldName)) { + privilegeToStatus.put(privilege, (parser.booleanValue()) ? Status.CREATED : Status.UPDATED); + } + } + } + } + } + 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 new file mode 100644 index 0000000000000..b294c878bd075 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java @@ -0,0 +1,186 @@ +/* + * 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.Nullable; +import org.elasticsearch.common.ParseField; +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; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +/** + * Represents an application specific privilege. The application name, privilege name, + * actions and metadata are completely managed by the client and can contain arbitrary + * string values. + */ +public final class ApplicationPrivilege implements ToXContentObject { + + private static final ParseField APPLICATION = new ParseField("application"); + private static final ParseField NAME = new ParseField("name"); + private static final ParseField ACTIONS = new ParseField("actions"); + private static final ParseField METADATA = new ParseField("metadata"); + + private final String application; + private final String name; + private final Set actions; + private final Map metadata; + + public ApplicationPrivilege(String application, String name, Collection actions, @Nullable Map metadata) { + if (Strings.isNullOrEmpty(application)) { + throw new IllegalArgumentException("application name must be provided"); + } else { + this.application = application; + } + if (Strings.isNullOrEmpty(name)) { + throw new IllegalArgumentException("privilege name must be provided"); + } else { + this.name = name; + } + if (actions == null || actions.isEmpty()) { + throw new IllegalArgumentException("actions must be provided"); + } else { + this.actions = Collections.unmodifiableSet(new HashSet<>(actions)); + } + if (metadata == null || metadata.isEmpty()) { + this.metadata = Collections.emptyMap(); + } else { + this.metadata = Collections.unmodifiableMap(metadata); + } + } + + public String getApplication() { + return application; + } + + public String getName() { + return name; + } + + public Set getActions() { + return actions; + } + + public Map getMetadata() { + return metadata; + } + + @SuppressWarnings("unchecked") + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "application_privilege", + true, args -> new ApplicationPrivilege((String) args[0], (String) args[1], (Collection) args[2], + (Map) args[3])); + + static { + PARSER.declareString(constructorArg(), APPLICATION); + PARSER.declareString(constructorArg(), NAME); + PARSER.declareStringArray(constructorArg(), ACTIONS); + PARSER.declareField(optionalConstructorArg(), XContentParser::map, ApplicationPrivilege.METADATA, ObjectParser.ValueType.OBJECT); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ApplicationPrivilege that = (ApplicationPrivilege) o; + return Objects.equals(application, that.application) && + Objects.equals(name, that.name) && + Objects.equals(actions, that.actions) && + Objects.equals(metadata, that.metadata); + } + + @Override + public int hashCode() { + return Objects.hash(application, name, actions, metadata); + } + + static ApplicationPrivilege fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder { + private String applicationName = null; + private String privilegeName = null; + private Collection actions = null; + private Map metadata = null; + + private Builder() { + } + + public Builder application(String applicationName) { + this.applicationName = Objects.requireNonNull(applicationName, "application name must be provided"); + return this; + } + + public Builder privilege(String privilegeName) { + this.privilegeName = Objects.requireNonNull(privilegeName, "privilege name must be provided"); + return this; + } + + public Builder actions(String... actions) { + this.actions = Arrays.asList(Objects.requireNonNull(actions)); + return this; + } + + public Builder actions(Collection actions) { + this.actions = Objects.requireNonNull(actions); + return this; + } + + public Builder metadata(Map metadata) { + this.metadata = metadata; + return this; + } + + public ApplicationPrivilege build() { + return new ApplicationPrivilege(applicationName, privilegeName, actions, metadata); + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject() + .field(APPLICATION.getPreferredName(), application) + .field(NAME.getPreferredName(), name) + .field(ACTIONS.getPreferredName(), actions) + .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 199356de2902e..2de6afead568f 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; @@ -30,7 +31,7 @@ import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.GetRoleMappingsRequest; -import org.elasticsearch.client.security.ChangePasswordRequest; +import org.elasticsearch.client.security.PutPrivilegesRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.RefreshPolicy; @@ -38,10 +39,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; @@ -243,6 +247,27 @@ public void testCreateTokenWithClientCredentialsGrant() throws Exception { assertToXContentBody(createTokenRequest, 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 39f57706a3667..2163f71688a3f 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 @@ -55,6 +55,9 @@ 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.PutPrivilegesResponse.Status; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.PutUserRequest; @@ -65,6 +68,7 @@ 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.client.security.user.privileges.IndicesPrivileges; import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.set.Sets; @@ -73,6 +77,7 @@ import org.hamcrest.Matchers; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; @@ -987,6 +992,74 @@ 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()); + 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 Status status = putPrivilegesResponse.status(applicationName, privilegeName); // <1> + // end::put-privileges-response + assertThat(status, is(Status.CREATED)); + } + + { + 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().status("app01", "all"), is(Status.UPDATED)); + } + } + public void testDeletePrivilege() throws Exception { RestHighLevelClient client = highLevelClient(); { 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..86fc94fa95b46 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesRequestTests.java @@ -0,0 +1,124 @@ +/* + * 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 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(), equalTo(privileges)); + assertThat(putPrivilegesRequest.getRefreshPolicy(), equalTo(refreshPolicy)); + } + } + + public void testToXContent() throws IOException { + final String expected = "{\"app01\":{\"all\":{\"application\":\"app01\",\"name\":\"all\",\"actions\":" + + "[\"action:logout\",\"action:login\"],\"metadata\":{\"k1\":\"v1\"}}}," + + "\"app02\":{\"all\":{\"application\":\"app02\",\"name\":\"all\",\"actions\":" + + "[\"action:logout\",\"action:login\"],\"metadata\":{\"k2\":\"v2\"}}}}"; + 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("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(); + assertThat(Strings.toString(putPrivilegesRequest.toXContent(builder, ToXContent.EMPTY_PARAMS)), equalTo(expected)); + } + + public void testEqualsHashCode() { + final 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("all") + .actions(Sets.newHashSet("action:login", "action:logout")) + .metadata(Collections.singletonMap("k1", "v1")) + .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(), 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: + return new PutPrivilegesRequest(original.getPrivileges().subList(0, 1), original.getRefreshPolicy()); + case 1: + return new PutPrivilegesRequest(original.getPrivileges(), randomFrom(policies)); + default: + return new PutPrivilegesRequest(original.getPrivileges(), 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..a67d541ed5f00 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java @@ -0,0 +1,83 @@ +/* + * 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.PutPrivilegesResponse.Status; +import org.elasticsearch.common.xcontent.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +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( + XContentType.JSON.xContent().createParser(new NamedXContentRegistry(Collections.emptyList()), new DeprecationHandler() { + @Override + public void usedDeprecatedName(String usedName, String modernName) { + } + + @Override + public void usedDeprecatedField(String usedName, String replacedWith) { + } + }, json)); + + assertThat(putPrivilegesResponse.status("app02", "all"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("app01", "read"), is(Status.UPDATED)); + assertThat(putPrivilegesResponse.status("app01", "write"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("unknown-app", "unknown-priv"), is(Status.UNKNOWN)); + assertThat(putPrivilegesResponse.status("app01", "unknown-priv"), is(Status.UNKNOWN)); + } + + public void testGetStatusFailsForInvalidApplicationOrPrivilegeName() { + final PutPrivilegesResponse putPrivilegesResponse = new PutPrivilegesResponse( + Collections.singletonMap("app-1", Collections.singletonMap("priv", Status.CREATED))); + final boolean nullOrEmptyAppName = randomBoolean(); + final String applicationName = nullOrEmptyAppName ? randomFrom(Arrays.asList("", " ", null)) : randomAlphaOfLength(4); + final String privilegeName = nullOrEmptyAppName ? randomAlphaOfLength(4) : randomFrom(Arrays.asList("", " ", null)); + IllegalArgumentException ile = expectThrows(IllegalArgumentException.class, + () -> putPrivilegesResponse.status(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 new file mode 100644 index 0000000000000..b720187673023 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java @@ -0,0 +1,118 @@ +/* + * 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.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; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; + +public class ApplicationPrivilegeTests extends ESTestCase { + + public void testFromXContentAndToXContent() throws IOException { + String json = + "{\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 + public void usedDeprecatedName(String usedName, String modernName) { + } + + @Override + public void usedDeprecatedField(String usedName, String replacedWith) { + } + }, json)); + final Map metadata = new HashMap<>(); + metadata.put("description", "Read access to myapp"); + 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() { + final Map metadata = new HashMap<>(); + metadata.put("description", "Read access to myapp"); + final String applicationName = randomBoolean() ? null : ""; + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> + new ApplicationPrivilege(applicationName, "read", Arrays.asList("data:read/*", "action:login"), metadata)); + assertThat(e.getMessage(), equalTo("application name must be provided")); + } + + public void testEmptyPrivilegeName() { + final Map metadata = new HashMap<>(); + metadata.put("description", "Read access to myapp"); + final String privilegenName = randomBoolean() ? null : ""; + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> + new ApplicationPrivilege("myapp", privilegenName, Arrays.asList("data:read/*", "action:login"), metadata)); + assertThat(e.getMessage(), equalTo("privilege name must be provided")); + } + + public void testEmptyActions() { + final Map metadata = new HashMap<>(); + metadata.put("description", "Read access to myapp"); + final List actions = randomBoolean() ? null : Collections.emptyList(); + final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> + new ApplicationPrivilege("myapp", "read", actions, metadata)); + assertThat(e.getMessage(), equalTo("actions must be provided")); + } + + public void testBuilder() { + final Map metadata = new HashMap<>(); + metadata.put("description", "Read access to myapp"); + ApplicationPrivilege privilege = ApplicationPrivilege.builder() + .application("myapp") + .privilege("read") + .actions("data:read/*", "action:login") + .metadata(metadata) + .build(); + assertThat(privilege.getApplication(), equalTo("myapp")); + assertThat(privilege.getName(), equalTo("read")); + assertThat(privilege.getActions(), containsInAnyOrder("data:read/*", "action:login")); + assertThat(privilege.getMetadata(), equalTo(metadata)); + } +} 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..cd612b78bda32 --- /dev/null +++ b/docs/java-rest/high-level/security/put-privileges.asciidoc @@ -0,0 +1,40 @@ +-- +: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}+ as `CREATED`, `UPDATED` +or `UNKNOWN`. + +["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. If the specified application name or privilege name +was not present in the +{request}+ then the status is `UNKNOWN` else +it will either be `CREATED` or `UPDATED`. \ No newline at end of file diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index 5fa05135cc035..12a5d21c58af4 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -358,6 +358,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <<{upid}-invalidate-token>> +* <<{upid}-put-privileges>> * <<{upid}-delete-privileges>> include::security/put-user.asciidoc[] @@ -376,6 +377,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 From 7f8e4b170373855b9f5b64317d5fdc96e882443a Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 19 Nov 2018 15:23:04 +1100 Subject: [PATCH 02/11] Remove unused import. --- .../org/elasticsearch/client/security/PutPrivilegesResponse.java | 1 - 1 file changed, 1 deletion(-) 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 index 99e09d1bc6dfb..d4abe1452247d 100644 --- 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 @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; From 66be0a0911031e4d037c3ca577f566caffa6ddf3 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 22 Nov 2018 09:47:21 +1100 Subject: [PATCH 03/11] Address review comments --- .../src/main/java/org/elasticsearch/client/SecurityClient.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 2f9ebefbf4910..da643a8022634 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 @@ -523,7 +523,8 @@ public PutPrivilegesResponse putPrivileges(final PutPrivilegesRequest request, f } /** - * Asynchronously create or update application privileges. See + * See * the docs for more. * From 99b26474a9674326497e4c0f526bab96ead7847c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 22 Nov 2018 10:09:51 +1100 Subject: [PATCH 04/11] Changes after merge - Corrected the toXContent for PutPrivilegesRequest for multiple privileges in application. - Changes to address TODO in testGetPrivileges - modified tests to test the scenario that got missed earlier --- .../client/SecurityRequestConverters.java | 8 +- .../client/security/PutPrivilegesRequest.java | 22 ++++-- .../user/privileges/ApplicationPrivilege.java | 6 +- .../SecurityDocumentationIT.java | 77 +++++++++++------- .../security/PutPrivilegesRequestTests.java | 79 +++++++++++++++---- 5 files changed, 129 insertions(+), 63 deletions(-) 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 1e0d02cd541c7..e0fc1837d1163 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,18 +28,12 @@ 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.HasPrivilegesRequest; -import org.elasticsearch.client.security.InvalidateTokenRequest; -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.InvalidateTokenRequest; -import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.HasPrivilegesRequest; import org.elasticsearch.client.security.InvalidateTokenRequest; import org.elasticsearch.client.security.PutPrivilegesRequest; 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 index 3b343a4d4ac20..e06c4caca3f35 100644 --- 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 @@ -28,25 +28,31 @@ 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.Collector; +import java.util.stream.Collectors; /** * Request object for creating/updating application privileges. */ public final class PutPrivilegesRequest implements Validatable, ToXContentObject { - private final List privileges; + 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.unmodifiableList(privileges); + this.privileges = Collections.unmodifiableMap(privileges.stream() + .collect(Collectors.groupingBy(ApplicationPrivilege::getApplication, TreeMap::new, Collectors.toList()))); this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.IMMEDIATE : refreshPolicy; } - public List getPrivileges() { + public Map> getPrivileges() { return privileges; } @@ -74,11 +80,13 @@ public boolean equals(Object o) { @Override public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { builder.startObject(); - for (ApplicationPrivilege privilege : privileges) { - builder.field(privilege.getApplication()); + for (Entry> entry : privileges.entrySet()) { + builder.field(entry.getKey()); builder.startObject(); - builder.field(privilege.getName()); - privilege.toXContent(builder, params); + 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/user/privileges/ApplicationPrivilege.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java index b294c878bd075..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 @@ -178,8 +178,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject() .field(APPLICATION.getPreferredName(), application) .field(NAME.getPreferredName(), name) - .field(ACTIONS.getPreferredName(), actions) - .field(METADATA.getPreferredName(), metadata); + .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/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index 50cdd997cdbee..72d397b9cfed9 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 @@ -1017,36 +1017,48 @@ 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(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/*") + .metadata(Collections.singletonMap("key1", "value1")) + .build()); + applicationPrivileges.add(ApplicationPrivilege.builder() + .application("testapp") + .privilege("all") + .actions("action:login", "data:write/*", "manage:*") + .build()); + applicationPrivileges.add(ApplicationPrivilege.builder() + .application("testapp2") + .privilege("read") + .actions("action:login", "data:read/*") + .metadata(Collections.singletonMap("key2", "value2")) + .build()); + applicationPrivileges.add(ApplicationPrivilege.builder() + .application("testapp2") + .privilege("write") + .actions("action:login", "data:write/*") + .build()); + applicationPrivileges.add(ApplicationPrivilege.builder() + .application("testapp2") + .privilege("all") + .actions("action:login", "data:write/*", "manage:*") + .build()); + PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(applicationPrivileges, RefreshPolicy.IMMEDIATE); + PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest, RequestOptions.DEFAULT); + assertNotNull(putPrivilegesResponse); + assertThat(putPrivilegesResponse.status("testapp", "write"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp", "read"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp", "all"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp2", "all"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp2", "write"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp2", "read"), is(Status.CREATED)); } { @@ -1151,6 +1163,11 @@ public void testPutPrivileges() throws Exception { .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 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 index 86fc94fa95b46..37a17544814bb 100644 --- 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 @@ -34,6 +34,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.equalTo; @@ -61,10 +62,40 @@ public void testConstructor() { } public void testToXContent() throws IOException { - final String expected = "{\"app01\":{\"all\":{\"application\":\"app01\",\"name\":\"all\",\"actions\":" - + "[\"action:logout\",\"action:login\"],\"metadata\":{\"k1\":\"v1\"}}}," - + "\"app02\":{\"all\":{\"application\":\"app02\",\"name\":\"all\",\"actions\":" - + "[\"action:logout\",\"action:login\"],\"metadata\":{\"k2\":\"v2\"}}}}"; + 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") @@ -72,6 +103,11 @@ public void testToXContent() throws IOException { .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") @@ -80,23 +116,23 @@ public void testToXContent() throws IOException { .build()); final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values()); final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy); - final XContentBuilder builder = XContentFactory.jsonBuilder(); + 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("app01") - .privilege("all") - .actions(Sets.newHashSet("action:login", "action:logout")) - .metadata(Collections.singletonMap("k1", "v1")) + .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("app01") - .privilege("all") - .actions(Sets.newHashSet("action:login", "action:logout")) - .metadata(Collections.singletonMap("k1", "v1")) + .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); @@ -105,7 +141,8 @@ public void testEqualsHashCode() { return new PutPrivilegesRequest(privileges, refreshPolicy); }); EqualsHashCodeTestUtils.checkEqualsAndHashCode(putPrivilegesRequest, (original) -> { - return new PutPrivilegesRequest(original.getPrivileges(), original.getRefreshPolicy()); + return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()), + original.getRefreshPolicy()); }, PutPrivilegesRequestTests::mutateTestItem); } @@ -114,11 +151,19 @@ private static PutPrivilegesRequest mutateTestItem(PutPrivilegesRequest original policies.remove(original.getRefreshPolicy()); switch (randomIntBetween(0, 1)) { case 0: - return new PutPrivilegesRequest(original.getPrivileges().subList(0, 1), original.getRefreshPolicy()); + 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(), randomFrom(policies)); + return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()), + randomFrom(policies)); default: - return new PutPrivilegesRequest(original.getPrivileges(), randomFrom(policies)); + return new PutPrivilegesRequest(original.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()), + randomFrom(policies)); } } } \ No newline at end of file From 9f5219bdecd4fe01aea12c5a151515e8bbc587c8 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 22 Nov 2018 12:42:21 +1100 Subject: [PATCH 05/11] precommit, removed unused import --- .../org/elasticsearch/client/security/PutPrivilegesRequest.java | 1 - 1 file changed, 1 deletion(-) 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 index e06c4caca3f35..477611f098721 100644 --- 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 @@ -32,7 +32,6 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.TreeMap; -import java.util.stream.Collector; import java.util.stream.Collectors; /** From 8048adb07abdbd5224e33b9bf1c3ad372193548c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 22 Nov 2018 15:40:44 +1100 Subject: [PATCH 06/11] Update delete privileges to avoid using low level client +Fix random test failure --- .../SecurityDocumentationIT.java | 79 +++++++------------ .../security/PutPrivilegesRequestTests.java | 3 +- 2 files changed, 32 insertions(+), 50 deletions(-) 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 72d397b9cfed9..eec5b99ef2f6c 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 @@ -1018,40 +1018,15 @@ public void testGetPrivileges() throws Exception { { 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/*") - .metadata(Collections.singletonMap("key1", "value1")) - .build()); - applicationPrivileges.add(ApplicationPrivilege.builder() - .application("testapp") - .privilege("all") - .actions("action:login", "data:write/*", "manage:*") - .build()); - applicationPrivileges.add(ApplicationPrivilege.builder() - .application("testapp2") - .privilege("read") - .actions("action:login", "data:read/*") - .metadata(Collections.singletonMap("key2", "value2")) - .build()); - applicationPrivileges.add(ApplicationPrivilege.builder() - .application("testapp2") - .privilege("write") - .actions("action:login", "data:write/*") - .build()); - applicationPrivileges.add(ApplicationPrivilege.builder() - .application("testapp2") - .privilege("all") - .actions("action:login", "data:write/*", "manage:*") - .build()); + 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.status("testapp", "write"), is(Status.CREATED)); assertThat(putPrivilegesResponse.status("testapp", "read"), is(Status.CREATED)); @@ -1227,23 +1202,29 @@ public void onFailure(Exception e) { 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.status("testapp", "write"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp", "read"), is(Status.CREATED)); + assertThat(putPrivilegesResponse.status("testapp", "all"), is(Status.CREATED)); } { // 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 index 37a17544814bb..8c60382eeec38 100644 --- 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 @@ -56,7 +56,8 @@ public void testConstructor() { assertThat(ile.getMessage(), equalTo("privileges are required")); } else { final PutPrivilegesRequest putPrivilegesRequest = new PutPrivilegesRequest(privileges, refreshPolicy); - assertThat(putPrivilegesRequest.getPrivileges(), equalTo(privileges)); + assertThat(putPrivilegesRequest.getPrivileges().values().stream().flatMap(List::stream).collect(Collectors.toList()), + equalTo(privileges)); assertThat(putPrivilegesRequest.getRefreshPolicy(), equalTo(refreshPolicy)); } } From 0619451220f343ceaa02b949e5a3f750e8f8c751 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Thu, 22 Nov 2018 16:46:23 +1100 Subject: [PATCH 07/11] uff, forgot to run precommit --- .../client/documentation/SecurityDocumentationIT.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 eec5b99ef2f6c..f6a3d3b101e58 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 @@ -28,7 +28,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; @@ -75,12 +74,11 @@ 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 java.io.IOException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; From 9f5b178591b4466e35091da2c56f8ad5332c3b53 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 27 Nov 2018 23:36:46 +1100 Subject: [PATCH 08/11] Address review comments --- .../client/security/PutPrivilegesRequest.java | 4 ++ .../security/PutPrivilegesResponse.java | 69 +++++++++---------- .../SecurityDocumentationIT.java | 25 ++++--- .../security/PutPrivilegesResponseTests.java | 49 ++++++------- 4 files changed, 74 insertions(+), 73 deletions(-) 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 index 477611f098721..3e20405046a0c 100644 --- 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 @@ -51,6 +51,10 @@ public PutPrivilegesRequest(final List privileges, @Nullab this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.IMMEDIATE : refreshPolicy; } + /** + * @return a map of application name to list of + * {@link ApplicationPrivilege}s + */ public Map> getPrivileges() { return privileges; } 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 index d4abe1452247d..ce89cb67df62f 100644 --- 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 @@ -19,6 +19,7 @@ package org.elasticsearch.client.security; +import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentParser; @@ -26,21 +27,21 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; - -import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import java.util.Map.Entry; /** * Response when creating/updating one or more application privileges to the * security index. */ public final class PutPrivilegesResponse { - public enum Status { - CREATED, UPDATED, UNKNOWN - } - private final Map> applicationPrivilegesCreatedOrUpdated; + /* + * 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) { + public PutPrivilegesResponse(final Map> applicationPrivilegesCreatedOrUpdated) { this.applicationPrivilegesCreatedOrUpdated = Collections.unmodifiableMap(applicationPrivilegesCreatedOrUpdated); } @@ -50,51 +51,45 @@ public PutPrivilegesResponse(final Map> applicationP * * @param applicationName application name as specified in the request * @param privilegeName privilege name as specified in the request - * @return {@link Status#CREATED} if the privilege was created, - * {@link Status#UPDATED} if the privilege was updated and for unknown - * application name / privilege name the status is {@link Status#UNKNOWN} + * @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 Status status(final String applicationName, final String privilegeName) { + 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"); } - return applicationPrivilegesCreatedOrUpdated.getOrDefault(applicationName, Collections.emptyMap()) - .getOrDefault(privilegeName, Status.UNKNOWN); + 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<>(); + final Map> applicationPrivilegesCreatedOrUpdated = new HashMap<>(); XContentParser.Token token = parser.currentToken(); if (token == null) { token = parser.nextToken(); } - ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - final String application = parser.currentName(); - final Map privilegeToStatus = applicationPrivilegesCreatedOrUpdated.computeIfAbsent(application, - (a) -> new HashMap<>()); - - token = parser.nextToken(); - ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - final String privilege = parser.currentName(); - token = parser.nextToken(); - ensureExpectedToken(XContentParser.Token.START_OBJECT, token, parser::getTokenLocation); - String currentFieldName = null; - while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - if (token == XContentParser.Token.FIELD_NAME) { - currentFieldName = parser.currentName(); - } else if (token == XContentParser.Token.VALUE_BOOLEAN) { - if ("created".equals(currentFieldName)) { - privilegeToStatus.put(privilege, (parser.booleanValue()) ? Status.CREATED : Status.UPDATED); - } - } + 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<>()); + if (entry.getValue() instanceof Map) { + Map createdOrUpdated = (Map) entry.getValue(); + createdOrUpdated.forEach((k, v) -> privilegeToStatus.put(k, ((Map) v).get("created"))); + } 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/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index f6a3d3b101e58..4179d7746ce4f 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 @@ -58,7 +58,6 @@ import org.elasticsearch.client.security.InvalidateTokenResponse; import org.elasticsearch.client.security.PutPrivilegesRequest; import org.elasticsearch.client.security.PutPrivilegesResponse; -import org.elasticsearch.client.security.PutPrivilegesResponse.Status; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; import org.elasticsearch.client.security.PutUserRequest; @@ -1026,12 +1025,12 @@ public void testGetPrivileges() throws Exception { PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest, RequestOptions.DEFAULT); assertNotNull(putPrivilegesResponse); - assertThat(putPrivilegesResponse.status("testapp", "write"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp", "read"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp", "all"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp2", "all"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp2", "write"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp2", "read"), is(Status.CREATED)); + 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)); } { @@ -1152,9 +1151,9 @@ public void testPutPrivileges() throws Exception { final String applicationName = "app01"; final String privilegeName = "all"; // tag::put-privileges-response - final Status status = putPrivilegesResponse.status(applicationName, privilegeName); // <1> + final boolean status = putPrivilegesResponse.wasCreated(applicationName, privilegeName); // <1> // end::put-privileges-response - assertThat(status, is(Status.CREATED)); + assertThat(status, is(true)); } { @@ -1193,7 +1192,7 @@ public void onFailure(Exception e) { //end::put-privileges-execute-async assertNotNull(future.get(30, TimeUnit.SECONDS)); - assertThat(future.get().status("app01", "all"), is(Status.UPDATED)); + assertThat(future.get().wasCreated("app01", "all"), is(false)); } } @@ -1220,9 +1219,9 @@ public void testDeletePrivilege() throws Exception { PutPrivilegesResponse putPrivilegesResponse = client.security().putPrivileges(putPrivilegesRequest, RequestOptions.DEFAULT); assertNotNull(putPrivilegesResponse); - assertThat(putPrivilegesResponse.status("testapp", "write"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp", "read"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("testapp", "all"), is(Status.CREATED)); + 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/PutPrivilegesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutPrivilegesResponseTests.java index a67d541ed5f00..9a39f456986e5 100644 --- 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 @@ -19,9 +19,6 @@ package org.elasticsearch.client.security; -import org.elasticsearch.client.security.PutPrivilegesResponse.Status; -import org.elasticsearch.common.xcontent.DeprecationHandler; -import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; @@ -51,32 +48,38 @@ public void testFromXContent() throws IOException { " }\n" + "}"; - final PutPrivilegesResponse putPrivilegesResponse = PutPrivilegesResponse.fromXContent( - XContentType.JSON.xContent().createParser(new NamedXContentRegistry(Collections.emptyList()), new DeprecationHandler() { - @Override - public void usedDeprecatedName(String usedName, String modernName) { - } + final PutPrivilegesResponse putPrivilegesResponse = PutPrivilegesResponse + .fromXContent(createParser(XContentType.JSON.xContent(), json)); - @Override - public void usedDeprecatedField(String usedName, String replacedWith) { - } - }, 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))); - assertThat(putPrivilegesResponse.status("app02", "all"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("app01", "read"), is(Status.UPDATED)); - assertThat(putPrivilegesResponse.status("app01", "write"), is(Status.CREATED)); - assertThat(putPrivilegesResponse.status("unknown-app", "unknown-priv"), is(Status.UNKNOWN)); - assertThat(putPrivilegesResponse.status("app01", "unknown-priv"), is(Status.UNKNOWN)); + 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 testGetStatusFailsForInvalidApplicationOrPrivilegeName() { + public void testGetStatusFailsForNullOrEmptyApplicationOrPrivilegeName() { final PutPrivilegesResponse putPrivilegesResponse = new PutPrivilegesResponse( - Collections.singletonMap("app-1", Collections.singletonMap("priv", Status.CREATED))); + Collections.singletonMap("app-1", Collections.singletonMap("priv", true))); + final boolean nullOrEmptyAppName = randomBoolean(); - final String applicationName = nullOrEmptyAppName ? randomFrom(Arrays.asList("", " ", null)) : randomAlphaOfLength(4); - final String privilegeName = nullOrEmptyAppName ? randomAlphaOfLength(4) : randomFrom(Arrays.asList("", " ", null)); - IllegalArgumentException ile = expectThrows(IllegalArgumentException.class, - () -> putPrivilegesResponse.status(applicationName, privilegeName)); + 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"))); } From 569059171c84f7153af8c0532c53c7ade6494272 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Wed, 28 Nov 2018 09:51:47 +1100 Subject: [PATCH 09/11] Address feedback, correct documentation --- .../high-level/security/put-privileges.asciidoc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/java-rest/high-level/security/put-privileges.asciidoc b/docs/java-rest/high-level/security/put-privileges.asciidoc index cd612b78bda32..1c0a97d2a94c3 100644 --- a/docs/java-rest/high-level/security/put-privileges.asciidoc +++ b/docs/java-rest/high-level/security/put-privileges.asciidoc @@ -27,14 +27,13 @@ include::../execution.asciidoc[] ==== Put Privileges Response The returned +{response}+ contains the information about the status -for each privilege present in the +{request}+ as `CREATED`, `UPDATED` -or `UNKNOWN`. +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. If the specified application name or privilege name -was not present in the +{request}+ then the status is `UNKNOWN` else -it will either be `CREATED` or `UPDATED`. \ No newline at end of file +privilege name. The status would be `true` if the privilege was created, +`false` if the privilege was updated. From 5e358e4073ec67044136e3e7e24167e916ad301c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Mon, 3 Dec 2018 21:46:44 +1100 Subject: [PATCH 10/11] Remove duplicate check --- .../client/security/PutPrivilegesResponse.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) 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 index ce89cb67df62f..c6c1f8f6dbeb3 100644 --- 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 @@ -82,12 +82,8 @@ public static PutPrivilegesResponse fromXContent(final XContentParser parser) th if (entry.getValue() instanceof Map) { final Map privilegeToStatus = applicationPrivilegesCreatedOrUpdated.computeIfAbsent(entry.getKey(), (a) -> new HashMap<>()); - if (entry.getValue() instanceof Map) { - Map createdOrUpdated = (Map) entry.getValue(); - createdOrUpdated.forEach((k, v) -> privilegeToStatus.put(k, ((Map) v).get("created"))); - } else { - throw new ParsingException(parser.getTokenLocation(), "Failed to parse object, unexpected structure"); - } + Map createdOrUpdated = (Map) entry.getValue(); + createdOrUpdated.forEach((k, v) -> privilegeToStatus.put(k, ((Map) v).get("created"))); } else { throw new ParsingException(parser.getTokenLocation(), "Failed to parse object, unexpected structure"); } From e7f77f66b32739ea6a6afe42c2b556be2f6cea9f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 4 Dec 2018 17:25:02 +1100 Subject: [PATCH 11/11] Address review comment --- .../client/security/PutPrivilegesResponse.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) 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 index c6c1f8f6dbeb3..fadf6d155dc69 100644 --- 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 @@ -82,8 +82,20 @@ public static PutPrivilegesResponse fromXContent(final XContentParser parser) th if (entry.getValue() instanceof Map) { final Map privilegeToStatus = applicationPrivilegesCreatedOrUpdated.computeIfAbsent(entry.getKey(), (a) -> new HashMap<>()); - Map createdOrUpdated = (Map) entry.getValue(); - createdOrUpdated.forEach((k, v) -> privilegeToStatus.put(k, ((Map) v).get("created"))); + 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"); }