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..b6d4bd110bd79 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 @@ -38,6 +38,8 @@ import org.elasticsearch.client.security.DisableUserRequest; import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.EnableUserRequest; +import org.elasticsearch.client.security.GetPrivilegesRequest; +import org.elasticsearch.client.security.GetPrivilegesResponse; import org.elasticsearch.client.security.GetRoleMappingsRequest; import org.elasticsearch.client.security.GetRoleMappingsResponse; import org.elasticsearch.client.security.GetSslCertificatesRequest; @@ -505,10 +507,47 @@ public void invalidateTokenAsync(InvalidateTokenRequest request, RequestOptions InvalidateTokenResponse::fromXContent, listener, emptySet()); } + /** + * Synchronously get application privilege(s). + * See + * the docs for more. + * + * @param request {@link GetPrivilegesRequest} with the application name and the privilege name. + * If no application name is provided, information about all privileges for all applications is retrieved. + * If no privilege name is provided, information about all privileges of the specified application is retrieved. + * @param options the request options (e.g. headers), use + * {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response from the get privileges call + * @throws IOException in case there is a problem sending the request or + * parsing back the response + */ + public GetPrivilegesResponse getPrivileges(final GetPrivilegesRequest request, final RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getPrivileges, + options, GetPrivilegesResponse::fromXContent, emptySet()); + } + + /** + * Asynchronously get application privilege(s). + * See + * the docs for more. + * + * @param request {@link GetPrivilegesRequest} with the application name and the privilege name. + * If no application name is provided, information about all privileges for all applications is retrieved. + * If no privilege name is provided, information about all privileges of the specified application is retrieved. + * @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 getPrivilegesAsync(final GetPrivilegesRequest request, final RequestOptions options, + final ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getPrivileges, + options, GetPrivilegesResponse::fromXContent, listener, emptySet()); + } + /** * Removes application privilege(s) * See * the docs for more. + * * @param request the request with the application privilege to delete * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized * @return the response from the delete application privilege call @@ -523,12 +562,13 @@ public DeletePrivilegesResponse deletePrivileges(DeletePrivilegesRequest request * Asynchronously removes an application privilege * See * the docs for more. - * @param request the request with the application privilege to delete - * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * + * @param request the request with the application privilege to delete + * @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 deletePrivilegesAsync(DeletePrivilegesRequest request, RequestOptions options, - ActionListener listener) { + ActionListener listener) { restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deletePrivileges, options, DeletePrivilegesResponse::fromXContent, listener, singleton(404)); } 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..b07c68f999873 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,14 +28,15 @@ 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.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.GetRoleMappingsRequest; -import org.elasticsearch.client.security.InvalidateTokenRequest; -import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.SetUserEnabledRequest; import org.elasticsearch.common.Strings; @@ -181,6 +182,15 @@ static Request invalidateToken(InvalidateTokenRequest invalidateTokenRequest) th return request; } + static Request getPrivileges(GetPrivilegesRequest getPrivilegesRequest) { + String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_xpack/security/privilege") + .addPathPart(getPrivilegesRequest.getApplicationName()) + .addCommaSeparatedPathParts(getPrivilegesRequest.getPrivilegeNames()) + .build(); + return new Request(HttpGet.METHOD_NAME, endpoint); + } + 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/GetPrivilegesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetPrivilegesRequest.java new file mode 100644 index 0000000000000..53e3c8c571e28 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetPrivilegesRequest.java @@ -0,0 +1,93 @@ +/* + * 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.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.util.CollectionUtils; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Request object to get application privilege(s) + */ +public final class GetPrivilegesRequest implements Validatable { + private final String applicationName; + private final String[] privilegeNames; + + public GetPrivilegesRequest(@Nullable final String applicationName, @Nullable final String... privilegeNames) { + if ((CollectionUtils.isEmpty(privilegeNames) == false) && Strings.isNullOrEmpty(applicationName)) { + throw new IllegalArgumentException("privilege cannot be specified when application is missing"); + } + this.applicationName = applicationName; + this.privilegeNames = privilegeNames; + } + + /** + * Constructs a {@link GetPrivilegesRequest} to request all the privileges defined for all applications + */ + public static GetPrivilegesRequest getAllPrivileges() { + return new GetPrivilegesRequest(null); + } + + /** + * Constructs a {@link GetPrivilegesRequest} to request all the privileges defined for the specified {@code applicationName} + * + * @param applicationName the name of the application for which the privileges are requested + */ + public static GetPrivilegesRequest getApplicationPrivileges(String applicationName) { + if (Strings.isNullOrEmpty(applicationName)) { + throw new IllegalArgumentException("application name is required"); + } + return new GetPrivilegesRequest(applicationName); + } + + /** + * @return the name of the application for which to return certain privileges + */ + public String getApplicationName() { + return applicationName; + } + + /** + * @return an array of privilege names to return or null if all should be returned + */ + public String[] getPrivilegeNames() { + return privilegeNames; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetPrivilegesRequest that = (GetPrivilegesRequest) o; + return Objects.equals(applicationName, that.applicationName) && + Arrays.equals(privilegeNames, that.privilegeNames); + } + + @Override + public int hashCode() { + int result = Objects.hash(applicationName); + result = 31 * result + Arrays.hashCode(privilegeNames); + return result; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetPrivilegesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetPrivilegesResponse.java new file mode 100644 index 0000000000000..182f14ef00c51 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetPrivilegesResponse.java @@ -0,0 +1,77 @@ +/* + * 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.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParserUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Get application privileges response + */ +public final class GetPrivilegesResponse { + + private Set privileges; + + public Set getPrivileges() { + return privileges; + } + + public GetPrivilegesResponse(Collection privileges) { + this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges)); + } + + public static GetPrivilegesResponse fromXContent(XContentParser parser) throws IOException { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + List privileges = new ArrayList<>(); + XContentParser.Token token; + while ((token = parser.nextToken()) != null) { + if (token == XContentParser.Token.FIELD_NAME) { + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + privileges.add(ApplicationPrivilege.PARSER.parse(parser, null)); + } + } + } + return new GetPrivilegesResponse(new HashSet<>(privileges)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GetPrivilegesResponse that = (GetPrivilegesResponse) o; + return Objects.equals(privileges, that.privileges); + } + + @Override + public int hashCode() { + return Objects.hash(privileges); + } +} 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..1f5a4f08191cc --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilege.java @@ -0,0 +1,174 @@ +/* + * 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.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 { + + 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); + } + } + +} 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..eb1d030b0f6b8 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 @@ -29,6 +29,7 @@ import org.elasticsearch.client.security.DeleteRoleRequest; 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.ChangePasswordRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; @@ -243,6 +244,50 @@ public void testCreateTokenWithClientCredentialsGrant() throws Exception { assertToXContentBody(createTokenRequest, request.getEntity()); } + public void testGetApplicationPrivilege() throws Exception { + final String application = randomAlphaOfLength(6); + final String privilege = randomAlphaOfLength(4); + GetPrivilegesRequest getPrivilegesRequest = new GetPrivilegesRequest(application, privilege); + Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/privilege/" + application + "/" + privilege, request.getEndpoint()); + assertEquals(Collections.emptyMap(), request.getParameters()); + assertNull(request.getEntity()); + } + + public void testGetAllApplicationPrivileges() throws Exception { + final String application = randomAlphaOfLength(6); + GetPrivilegesRequest getPrivilegesRequest = GetPrivilegesRequest.getApplicationPrivileges(application); + Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/privilege/" + application, request.getEndpoint()); + assertEquals(Collections.emptyMap(), request.getParameters()); + assertNull(request.getEntity()); + } + + public void testGetMultipleApplicationPrivileges() throws Exception { + final String application = randomAlphaOfLength(6); + final int numberOfPrivileges = randomIntBetween(1, 5); + final String[] privilegeNames = + randomArray(numberOfPrivileges, numberOfPrivileges, String[]::new, () -> randomAlphaOfLength(5)); + GetPrivilegesRequest getPrivilegesRequest = new GetPrivilegesRequest(application, privilegeNames); + Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/privilege/" + application + "/" + Strings.arrayToCommaDelimitedString(privilegeNames), + request.getEndpoint()); + assertEquals(Collections.emptyMap(), request.getParameters()); + assertNull(request.getEntity()); + } + + public void testGetAllPrivileges() throws Exception { + GetPrivilegesRequest getPrivilegesRequest = GetPrivilegesRequest.getAllPrivileges(); + Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest); + assertEquals(HttpGet.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/privilege", request.getEndpoint()); + assertEquals(Collections.emptyMap(), request.getParameters()); + assertNull(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..519d410fd2e37 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 @@ -48,6 +48,8 @@ import org.elasticsearch.client.security.EmptyResponse; import org.elasticsearch.client.security.EnableUserRequest; import org.elasticsearch.client.security.ExpressionRoleMapping; +import org.elasticsearch.client.security.GetPrivilegesRequest; +import org.elasticsearch.client.security.GetPrivilegesResponse; import org.elasticsearch.client.security.GetRoleMappingsRequest; import org.elasticsearch.client.security.GetRoleMappingsResponse; import org.elasticsearch.client.security.GetSslCertificatesResponse; @@ -65,6 +67,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,16 +76,19 @@ import org.hamcrest.Matchers; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.equalTo; @@ -475,7 +481,7 @@ public void testHasPrivileges() throws Exception { } { - HasPrivilegesRequest request = new HasPrivilegesRequest(Collections.singleton("monitor"),null,null); + HasPrivilegesRequest request = new HasPrivilegesRequest(Collections.singleton("monitor"), null, null); // tag::has-privileges-execute-listener ActionListener listener = new ActionListener() { @@ -987,6 +993,146 @@ public void onFailure(Exception e) { } } + public void testGetPrivileges() throws Exception { + final RestHighLevelClient client = highLevelClient(); + final ApplicationPrivilege readTestappPrivilege = + new ApplicationPrivilege("testapp", "read", Arrays.asList("action:login", "data:read/*"), null); + final Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + final ApplicationPrivilege writeTestappPrivilege = + new ApplicationPrivilege("testapp", "write", Arrays.asList("action:login", "data:write/*"), metadata); + final ApplicationPrivilege allTestappPrivilege = + new ApplicationPrivilege("testapp", "all", Arrays.asList("action:login", "data:write/*", "manage:*"), null); + final Map metadata2 = new HashMap<>(); + metadata2.put("key2", "value2"); + final ApplicationPrivilege readTestapp2Privilege = + new ApplicationPrivilege("testapp2", "read", Arrays.asList("action:login", "data:read/*"), metadata2); + final ApplicationPrivilege writeTestapp2Privilege = + new ApplicationPrivilege("testapp2", "write", Arrays.asList("action:login", "data:write/*"), null); + final ApplicationPrivilege allTestapp2Privilege = + 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()); + } + + { + //tag::get-privileges-request + GetPrivilegesRequest request = new GetPrivilegesRequest("testapp", "write"); + //end::get-privileges-request + //tag::get-privileges-execute + GetPrivilegesResponse response = client.security().getPrivileges(request, RequestOptions.DEFAULT); + //end::get-privileges-execute + assertNotNull(response); + assertThat(response.getPrivileges().size(), equalTo(1)); + assertThat(response.getPrivileges().contains(writeTestappPrivilege), equalTo(true)); + } + + { + //tag::get-all-application-privileges-request + GetPrivilegesRequest request = GetPrivilegesRequest.getApplicationPrivileges("testapp"); + //end::get-all-application-privileges-request + GetPrivilegesResponse response = client.security().getPrivileges(request, RequestOptions.DEFAULT); + + assertNotNull(response); + assertThat(response.getPrivileges().size(), equalTo(3)); + final GetPrivilegesResponse exptectedResponse = + new GetPrivilegesResponse(Arrays.asList(readTestappPrivilege, writeTestappPrivilege, allTestappPrivilege)); + assertThat(response, equalTo(exptectedResponse)); + Set privileges = response.getPrivileges(); + for (ApplicationPrivilege privilege : privileges) { + assertThat(privilege.getApplication(), equalTo("testapp")); + if (privilege.getName().equals("read")) { + assertThat(privilege.getActions(), containsInAnyOrder("action:login", "data:read/*")); + assertThat(privilege.getMetadata().isEmpty(), equalTo(true)); + } else if (privilege.getName().equals("write")) { + assertThat(privilege.getActions(), containsInAnyOrder("action:login", "data:write/*")); + assertThat(privilege.getMetadata().isEmpty(), equalTo(false)); + assertThat(privilege.getMetadata().get("key1"), equalTo("value1")); + } else if (privilege.getName().equals("all")) { + assertThat(privilege.getActions(), containsInAnyOrder("action:login", "data:write/*", "manage:*")); + assertThat(privilege.getMetadata().isEmpty(), equalTo(true)); + } + } + } + + { + //tag::get-all-privileges-request + GetPrivilegesRequest request = GetPrivilegesRequest.getAllPrivileges(); + //end::get-all-privileges-request + GetPrivilegesResponse response = client.security().getPrivileges(request, RequestOptions.DEFAULT); + + assertNotNull(response); + assertThat(response.getPrivileges().size(), equalTo(6)); + final GetPrivilegesResponse exptectedResponse = + new GetPrivilegesResponse(Arrays.asList(readTestappPrivilege, writeTestappPrivilege, allTestappPrivilege, + readTestapp2Privilege, writeTestapp2Privilege, allTestapp2Privilege)); + assertThat(response, equalTo(exptectedResponse)); + } + + { + GetPrivilegesRequest request = new GetPrivilegesRequest("testapp", "read"); + //tag::get-privileges-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(GetPrivilegesResponse getPrivilegesResponse) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + //end::get-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::get-privileges-execute-async + client.security().getPrivilegesAsync(request, RequestOptions.DEFAULT, listener); // <1> + //end::get-privileges-execute-async + + final GetPrivilegesResponse response = future.get(30, TimeUnit.SECONDS); + assertNotNull(response); + assertThat(response.getPrivileges().size(), equalTo(1)); + assertThat(response.getPrivileges().contains(readTestappPrivilege), equalTo(true)); + } + } + public void testDeletePrivilege() throws Exception { RestHighLevelClient client = highLevelClient(); { @@ -1061,3 +1207,4 @@ public void onFailure(Exception e) { } } } + diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesRequestTests.java new file mode 100644 index 0000000000000..12638bef326c7 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesRequestTests.java @@ -0,0 +1,65 @@ +/* + * 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.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; + +import static org.hamcrest.Matchers.equalTo; + +public class GetPrivilegesRequestTests extends ESTestCase { + + public void testGetPrivilegesRequest() { + final String applicationName = randomAlphaOfLength(5); + final int numberOfPrivileges = randomIntBetween(0, 5); + final String[] privilegeNames = randomBoolean() ? null : randomArray(numberOfPrivileges, numberOfPrivileges, String[]::new, + () -> randomAlphaOfLength(5)); + final GetPrivilegesRequest getPrivilegesRequest = new GetPrivilegesRequest(applicationName, privilegeNames); + assertThat(getPrivilegesRequest.getApplicationName(), equalTo(applicationName)); + assertThat(getPrivilegesRequest.getPrivilegeNames(), equalTo(privilegeNames)); + } + + public void testPrivilegeWithoutApplication() { + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> { + new GetPrivilegesRequest(null, randomAlphaOfLength(5)); + }); + assertThat(e.getMessage(), equalTo("privilege cannot be specified when application is missing")); + } + + public void testEqualsAndHashCode() { + final String applicationName = randomAlphaOfLength(5); + final int numberOfPrivileges = randomIntBetween(0, 5); + final String[] privilegeNames = + randomArray(numberOfPrivileges, numberOfPrivileges, String[]::new, () -> randomAlphaOfLength(5)); + final GetPrivilegesRequest getPrivilegesRequest = new GetPrivilegesRequest(applicationName, privilegeNames); + final EqualsHashCodeTestUtils.MutateFunction mutate = r -> { + if (randomBoolean()) { + final int numberOfNewPrivileges = randomIntBetween(1, 5); + final String[] newPrivilegeNames = + randomArray(numberOfNewPrivileges, numberOfNewPrivileges, String[]::new, () -> randomAlphaOfLength(5)); + return new GetPrivilegesRequest(applicationName, newPrivilegeNames); + } else { + return GetPrivilegesRequest.getApplicationPrivileges(randomAlphaOfLength(6)); + } + }; + EqualsHashCodeTestUtils.checkEqualsAndHashCode(getPrivilegesRequest, + r -> new GetPrivilegesRequest(r.getApplicationName(), r.getPrivilegeNames()), mutate); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesResponseTests.java new file mode 100644 index 0000000000000..74211892a09e8 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetPrivilegesResponseTests.java @@ -0,0 +1,154 @@ +/* + * 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.xcontent.DeprecationHandler; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentType; +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.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; + +public class GetPrivilegesResponseTests extends ESTestCase { + + public void testFromXContent() throws IOException { + final String json = "{" + + " \"testapp\": {" + + " \"read\": {" + + " \"application\": \"testapp\"," + + " \"name\": \"read\"," + + " \"actions\": [ \"action:login\", \"data:read/*\" ]" + + " }," + + " \"write\": {" + + " \"application\": \"testapp\"," + + " \"name\": \"write\"," + + " \"actions\": [ \"action:login\", \"data:write/*\" ]," + + " \"metadata\": { \"key1\": \"value1\" }" + + " }," + + " \"all\": {" + + " \"application\": \"testapp\"," + + " \"name\": \"all\"," + + " \"actions\": [ \"action:login\", \"data:write/*\" , \"manage:*\"]" + + " }" + + " }," + + " \"testapp2\": {" + + " \"read\": {" + + " \"application\": \"testapp2\"," + + " \"name\": \"read\"," + + " \"actions\": [ \"action:login\", \"data:read/*\" ]," + + " \"metadata\": { \"key2\": \"value2\" }" + + " }," + + " \"write\": {" + + " \"application\": \"testapp2\"," + + " \"name\": \"write\"," + + " \"actions\": [ \"action:login\", \"data:write/*\" ]" + + " }," + + " \"all\": {" + + " \"application\": \"testapp2\"," + + " \"name\": \"all\"," + + " \"actions\": [ \"action:login\", \"data:write/*\" , \"manage:*\"]" + + " }" + + " }" + + "}"; + + final GetPrivilegesResponse response = GetPrivilegesResponse.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 ApplicationPrivilege readTestappPrivilege = + new ApplicationPrivilege("testapp", "read", Arrays.asList("action:login", "data:read/*"), null); + final Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + final ApplicationPrivilege writeTestappPrivilege = + new ApplicationPrivilege("testapp", "write", Arrays.asList("action:login", "data:write/*"), metadata); + final ApplicationPrivilege allTestappPrivilege = + new ApplicationPrivilege("testapp", "all", Arrays.asList("action:login", "data:write/*", "manage:*"), null); + final Map metadata2 = new HashMap<>(); + metadata2.put("key2", "value2"); + final ApplicationPrivilege readTestapp2Privilege = + new ApplicationPrivilege("testapp2", "read", Arrays.asList("action:login", "data:read/*"), metadata2); + final ApplicationPrivilege writeTestapp2Privilege = + new ApplicationPrivilege("testapp2", "write", Arrays.asList("action:login", "data:write/*"), null); + final ApplicationPrivilege allTestapp2Privilege = + new ApplicationPrivilege("testapp2", "all", Arrays.asList("action:login", "data:write/*", "manage:*"), null); + final GetPrivilegesResponse exptectedResponse = + new GetPrivilegesResponse(Arrays.asList(readTestappPrivilege, writeTestappPrivilege, allTestappPrivilege, + readTestapp2Privilege, writeTestapp2Privilege, allTestapp2Privilege)); + assertThat(response, equalTo(exptectedResponse)); + } + + public void testEqualsHashCode() { + final List privileges = new ArrayList<>(); + final List privileges2 = new ArrayList<>(); + final Map metadata = new HashMap<>(); + metadata.put("key1", "value1"); + final ApplicationPrivilege writePrivilege = + new ApplicationPrivilege("testapp", "write", Arrays.asList("action:login", "data:write/*"), + metadata); + final ApplicationPrivilege readPrivilege = + new ApplicationPrivilege("testapp", "read", Arrays.asList("data:read/*", "action:login"), + metadata); + privileges.add(readPrivilege); + privileges.add(writePrivilege); + privileges2.add(writePrivilege); + privileges2.add(readPrivilege); + final GetPrivilegesResponse response = new GetPrivilegesResponse(privileges); + EqualsHashCodeTestUtils.checkEqualsAndHashCode(response, (original) -> { + return new GetPrivilegesResponse(original.getPrivileges()); + }); + EqualsHashCodeTestUtils.checkEqualsAndHashCode(response, (original) -> { + return new GetPrivilegesResponse(original.getPrivileges()); + }, GetPrivilegesResponseTests::mutateTestItem); + } + + private static GetPrivilegesResponse mutateTestItem(GetPrivilegesResponse original) { + if (randomBoolean()) { + Set originalPrivileges = original.getPrivileges(); + List privileges = new ArrayList<>(); + privileges.addAll(originalPrivileges); + privileges.add(new ApplicationPrivilege("testapp", "all", Arrays.asList("action:login", "data:read/*", "manage:*"), null)); + return new GetPrivilegesResponse(privileges); + } else { + final List privileges = new ArrayList<>(); + final ApplicationPrivilege privilege = + new ApplicationPrivilege("testapp", "all", Arrays.asList("action:login", "data:write/*", "manage:*"), null); + privileges.add(privilege); + return new GetPrivilegesResponse(privileges); + } + } +} 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..f958cadaa7e80 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationPrivilegeTests.java @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.client.security.user.privileges; + +import org.elasticsearch.common.xcontent.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 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 testFromXContent() throws IOException { + String json = + " {" + + " \"application\": \"myapp\"," + + " \"name\": \"read\"," + + " \"actions\": [" + + " \"data:read/*\"," + + " \"action:login\"" + + " ],\n" + + " \"metadata\": {" + + " \"description\": \"Read access to myapp\"" + + " }" + + " }"; + 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)); + } + + 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/get-privileges.asciidoc b/docs/java-rest/high-level/security/get-privileges.asciidoc new file mode 100644 index 0000000000000..06ae51e669081 --- /dev/null +++ b/docs/java-rest/high-level/security/get-privileges.asciidoc @@ -0,0 +1,47 @@ + +-- +:api: get-privileges +:request: GetPrivilegesRequest +:respnse: GetPrivilegesResponse +-- + +[id="{upid}-{api}"] +=== Get Privileges API + +[id="{upid}-{api}-request"] +==== Get Privileges Request + +The +{request}+ supports getting privilege(s) for all or for specific applications. + +===== Specific privilege of a specific application + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- + +===== All privileges of a specific application + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[get-all-application-privileges-request] +-------------------------------------------------- + +===== All privileges of all applications + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[get-all-privileges-request] +-------------------------------------------------- + +include::../execution.asciidoc[] + +[id="{upid}-{api}-response"] +==== Get Privileges Response + +The returned +{response}+ allows to get information about the retrieved privileges as follows. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- \ 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 cabd20d52ff07..3e3606bdd6789 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -365,6 +365,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <<{upid}-invalidate-token>> +* <<{upid}-get-privileges>> * <<{upid}-delete-privileges>> include::security/put-user.asciidoc[] @@ -373,6 +374,7 @@ include::security/disable-user.asciidoc[] include::security/change-password.asciidoc[] include::security/delete-role.asciidoc[] include::security/delete-privileges.asciidoc[] +include::security/get-privileges.asciidoc[] include::security/clear-roles-cache.asciidoc[] include::security/clear-realm-cache.asciidoc[] include::security/authenticate.asciidoc[]