HLRC: Add "_has_privileges" API to Security Client (#35479)

This adds the "hasPrivileges()" method to SecurityClient, including
request, response & async variant of the method.

Also includes API documentation.
This commit is contained in:
Tim Vernum 2018-11-16 13:52:06 +11:00 committed by GitHub
parent 0cdfc4cd0a
commit 87a8b99724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 912 additions and 0 deletions

View File

@ -42,6 +42,8 @@ import org.elasticsearch.client.security.GetRoleMappingsRequest;
import org.elasticsearch.client.security.GetRoleMappingsResponse;
import org.elasticsearch.client.security.GetSslCertificatesRequest;
import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
@ -244,6 +246,34 @@ public final class SecurityClient {
AuthenticateResponse::fromXContent, listener, emptySet());
}
/**
* Determine whether the current user has a specified list of privileges
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-has-privileges.html">
* the docs</a> for more.
*
* @param request the request with the privileges to check
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response from the has privileges call
*/
public HasPrivilegesResponse hasPrivileges(HasPrivilegesRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::hasPrivileges, options,
HasPrivilegesResponse::fromXContent, emptySet());
}
/**
* Asynchronously determine whether the current user has a specified list of privileges
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-has-privileges.html">
* the docs</a> for more.
*
* @param request the request with the privileges to check
* @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 hasPrivilegesAsync(HasPrivilegesRequest request, RequestOptions options, ActionListener<HasPrivilegesResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::hasPrivileges, options,
HasPrivilegesResponse::fromXContent, listener, emptySet());
}
/**
* Clears the cache in one or more realms.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-clear-cache.html">

View File

@ -30,6 +30,7 @@ import org.elasticsearch.client.security.CreateTokenRequest;
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;
@ -114,6 +115,12 @@ final class SecurityRequestConverters {
return request;
}
static Request hasPrivileges(HasPrivilegesRequest hasPrivilegesRequest) throws IOException {
Request request = new Request(HttpGet.METHOD_NAME, "/_xpack/security/user/_has_privileges");
request.setEntity(createEntity(hasPrivilegesRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
static Request clearRealmCache(ClearRealmCacheRequest clearRealmCacheRequest) {
RequestConverters.EndpointBuilder builder = new RequestConverters.EndpointBuilder()
.addPathPartAsIs("_xpack/security/realm");

View File

@ -0,0 +1,96 @@
/*
* 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.ApplicationResourcePrivileges;
import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
/**
* Request to determine whether the current user has a list of privileges.
*/
public final class HasPrivilegesRequest implements Validatable, ToXContentObject {
private final Set<String> clusterPrivileges;
private final Set<IndicesPrivileges> indexPrivileges;
private final Set<ApplicationResourcePrivileges> applicationPrivileges;
public HasPrivilegesRequest(@Nullable Set<String> clusterPrivileges,
@Nullable Set<IndicesPrivileges> indexPrivileges,
@Nullable Set<ApplicationResourcePrivileges> applicationPrivileges) {
this.clusterPrivileges = clusterPrivileges == null ? emptySet() : unmodifiableSet(clusterPrivileges);
this.indexPrivileges = indexPrivileges == null ? emptySet() : unmodifiableSet(indexPrivileges);
this.applicationPrivileges = applicationPrivileges == null ? emptySet() : unmodifiableSet(applicationPrivileges);
if (this.clusterPrivileges.isEmpty() && this.indexPrivileges.isEmpty() && this.applicationPrivileges.isEmpty()) {
throw new IllegalArgumentException("At last 1 privilege must be specified");
}
}
public Set<String> getClusterPrivileges() {
return clusterPrivileges;
}
public Set<IndicesPrivileges> getIndexPrivileges() {
return indexPrivileges;
}
public Set<ApplicationResourcePrivileges> getApplicationPrivileges() {
return applicationPrivileges;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject()
.field("cluster", clusterPrivileges)
.field("index", indexPrivileges)
.field("application", applicationPrivileges)
.endObject();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final HasPrivilegesRequest that = (HasPrivilegesRequest) o;
return Objects.equals(clusterPrivileges, that.clusterPrivileges) &&
Objects.equals(indexPrivileges, that.indexPrivileges) &&
Objects.equals(applicationPrivileges, that.applicationPrivileges);
}
@Override
public int hashCode() {
return Objects.hash(clusterPrivileges, indexPrivileges, applicationPrivileges);
}
}

View File

@ -0,0 +1,252 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.security;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
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 java.util.function.BiConsumer;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/**
* Response when checking whether the current user has a defined set of privileges.
*/
public final class HasPrivilegesResponse {
private static final ConstructingObjectParser<HasPrivilegesResponse, Void> PARSER = new ConstructingObjectParser<>(
"has_privileges_response", true, args -> new HasPrivilegesResponse(
(String) args[0], (Boolean) args[1], checkMap(args[2], 0), checkMap(args[3], 1), checkMap(args[4], 2)));
static {
PARSER.declareString(constructorArg(), new ParseField("username"));
PARSER.declareBoolean(constructorArg(), new ParseField("has_all_requested"));
declareMap(constructorArg(), "cluster");
declareMap(constructorArg(), "index");
declareMap(constructorArg(), "application");
}
@SuppressWarnings("unchecked")
private static <T> Map<String, T> checkMap(Object argument, int depth) {
if (argument instanceof Map) {
Map<String, T> map = (Map<String, T>) argument;
if (depth == 0) {
map.values().stream()
.filter(val -> (val instanceof Boolean) == false)
.forEach(val -> {
throw new IllegalArgumentException("Map value [" + val + "] in [" + map + "] is not a Boolean");
});
} else {
map.values().stream().forEach(val -> checkMap(val, depth - 1));
}
return map;
}
throw new IllegalArgumentException("Value [" + argument + "] is not an Object");
}
private static void declareMap(BiConsumer<HasPrivilegesResponse, Map<String, Object>> arg, String name) {
PARSER.declareField(arg, XContentParser::map, new ParseField(name), ObjectParser.ValueType.OBJECT);
}
private final String username;
private final boolean hasAllRequested;
private final Map<String, Boolean> clusterPrivileges;
private final Map<String, Map<String, Boolean>> indexPrivileges;
private final Map<String, Map<String, Map<String, Boolean>>> applicationPrivileges;
public HasPrivilegesResponse(String username, boolean hasAllRequested,
Map<String, Boolean> clusterPrivileges,
Map<String, Map<String, Boolean>> indexPrivileges,
Map<String, Map<String, Map<String, Boolean>>> applicationPrivileges) {
this.username = username;
this.hasAllRequested = hasAllRequested;
this.clusterPrivileges = Collections.unmodifiableMap(clusterPrivileges);
this.indexPrivileges = unmodifiableMap2(indexPrivileges);
this.applicationPrivileges = unmodifiableMap3(applicationPrivileges);
}
private static Map<String, Map<String, Boolean>> unmodifiableMap2(final Map<String, Map<String, Boolean>> map) {
final Map<String, Map<String, Boolean>> copy = new HashMap<>(map);
copy.replaceAll((k, v) -> Collections.unmodifiableMap(v));
return Collections.unmodifiableMap(copy);
}
private static Map<String, Map<String, Map<String, Boolean>>> unmodifiableMap3(
final Map<String, Map<String, Map<String, Boolean>>> map) {
final Map<String, Map<String, Map<String, Boolean>>> copy = new HashMap<>(map);
copy.replaceAll((k, v) -> unmodifiableMap2(v));
return Collections.unmodifiableMap(copy);
}
public static HasPrivilegesResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
/**
* The username (principal) of the user for which the privileges check was executed.
*/
public String getUsername() {
return username;
}
/**
* {@code true} if the user has every privilege that was checked. Otherwise {@code false}.
*/
public boolean hasAllRequested() {
return hasAllRequested;
}
/**
* @param clusterPrivilegeName The name of a cluster privilege. This privilege must have been specified (verbatim) in the
* {@link HasPrivilegesRequest#getClusterPrivileges() cluster privileges of the request}.
* @return {@code true} if the user has the specified cluster privilege. {@code false} if the privilege was checked
* but it has not been granted to the user.
* @throws IllegalArgumentException if the response did not include a value for the specified privilege name.
* The response only includes values for privileges that were
* {@link HasPrivilegesRequest#getClusterPrivileges() included in the request}.
*/
public boolean hasClusterPrivilege(String clusterPrivilegeName) throws IllegalArgumentException {
Boolean has = clusterPrivileges.get(clusterPrivilegeName);
if (has == null) {
throw new IllegalArgumentException("Cluster privilege [" + clusterPrivilegeName + "] was not included in this response");
}
return has;
}
/**
* @param indexName The name of the index to check. This index must have been specified (verbatim) in the
* {@link HasPrivilegesRequest#getIndexPrivileges() requested index privileges}.
* @param privilegeName The name of the index privilege to check. This privilege must have been specified (verbatim), for the
* given index, in the {@link HasPrivilegesRequest#getIndexPrivileges() requested index privileges}.
* @return {@code true} if the user has the specified privilege on the specified index. {@code false} if the privilege was checked
* for that index and was not granted to the user.
* @throws IllegalArgumentException if the response did not include a value for the specified index and privilege name pair.
* The response only includes values for indices and privileges that were
* {@link HasPrivilegesRequest#getIndexPrivileges() included in the request}.
*/
public boolean hasIndexPrivilege(String indexName, String privilegeName) {
Map<String, Boolean> indexPrivileges = this.indexPrivileges.get(indexName);
if (indexPrivileges == null) {
throw new IllegalArgumentException("No privileges for index [" + indexName + "] were included in this response");
}
Boolean has = indexPrivileges.get(privilegeName);
if (has == null) {
throw new IllegalArgumentException("Privilege [" + privilegeName + "] was not included in the response for index ["
+ indexName + "]");
}
return has;
}
/**
* @param applicationName The name of the application to check. This application must have been specified (verbatim) in the
* {@link HasPrivilegesRequest#getApplicationPrivileges() requested application privileges}.
* @param resourceName The name of the resource to check. This resource must have been specified (verbatim), for the given
* application in the {@link HasPrivilegesRequest#getApplicationPrivileges() requested application privileges}.
* @param privilegeName The name of the privilege to check. This privilege must have been specified (verbatim), for the given
* application and resource, in the
* {@link HasPrivilegesRequest#getApplicationPrivileges() requested application privileges}.
* @return {@code true} if the user has the specified privilege on the specified resource in the specified application.
* {@code false} if the privilege was checked for that application and resource, but was not granted to the user.
* @throws IllegalArgumentException if the response did not include a value for the specified application, resource and privilege
* triplet. The response only includes values for applications, resources and privileges that were
* {@link HasPrivilegesRequest#getApplicationPrivileges() included in the request}.
*/
public boolean hasApplicationPrivilege(String applicationName, String resourceName, String privilegeName) {
final Map<String, Map<String, Boolean>> appPrivileges = this.applicationPrivileges.get(applicationName);
if (appPrivileges == null) {
throw new IllegalArgumentException("No privileges for application [" + applicationName + "] were included in this response");
}
final Map<String, Boolean> resourcePrivileges = appPrivileges.get(resourceName);
if (resourcePrivileges == null) {
throw new IllegalArgumentException("No privileges for resource [" + resourceName +
"] were included in the response for application [" + applicationName + "]");
}
Boolean has = resourcePrivileges.get(privilegeName);
if (has == null) {
throw new IllegalArgumentException("Privilege [" + privilegeName + "] was not included in the response for application [" +
applicationName + "] and resource [" + resourceName + "]");
}
return has;
}
/**
* A {@code Map} from cluster-privilege-name to access. Each requested privilege is included as a key in the map, and the
* associated value indicates whether the user was granted that privilege.
* <p>
* The {@link #hasClusterPrivilege} method should be used in preference to directly accessing this map.
* </p>
*/
public Map<String, Boolean> getClusterPrivileges() {
return clusterPrivileges;
}
/**
* A {@code Map} from index-name + privilege-name to access. Each requested index is a key in the outer map.
* Each requested privilege is a key in the inner map. The inner most {@code Boolean} value indicates whether
* the user was granted that privilege on that index.
* <p>
* The {@link #hasIndexPrivilege} method should be used in preference to directly accessing this map.
* </p>
*/
public Map<String, Map<String, Boolean>> getIndexPrivileges() {
return indexPrivileges;
}
/**
* A {@code Map} from application-name + resource-name + privilege-name to access. Each requested application is a key in the
* outer-most map. Each requested resource is a key in the next-level map. The requested privileges form the keys in the inner-most map.
* The {@code Boolean} value indicates whether the user was granted that privilege on that resource within that application.
* <p>
* The {@link #hasApplicationPrivilege} method should be used in preference to directly accessing this map.
* </p>
*/
public Map<String, Map<String, Map<String, Boolean>>> getApplicationPrivileges() {
return applicationPrivileges;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final HasPrivilegesResponse that = (HasPrivilegesResponse) o;
return this.hasAllRequested == that.hasAllRequested &&
Objects.equals(this.username, that.username) &&
Objects.equals(this.clusterPrivileges, that.clusterPrivileges) &&
Objects.equals(this.indexPrivileges, that.indexPrivileges) &&
Objects.equals(this.applicationPrivileges, that.applicationPrivileges);
}
@Override
public int hashCode() {
return Objects.hash(username, hasAllRequested, clusterPrivileges, indexPrivileges, applicationPrivileges);
}
}

View File

@ -51,6 +51,8 @@ import org.elasticsearch.client.security.ExpressionRoleMapping;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
import org.elasticsearch.client.security.GetRoleMappingsResponse;
import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
@ -63,7 +65,9 @@ import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpress
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.IndicesPrivileges;
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;
@ -80,6 +84,7 @@ import java.util.concurrent.TimeUnit;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isIn;
@ -437,6 +442,67 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
}
}
public void testHasPrivileges() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::has-privileges-request
HasPrivilegesRequest request = new HasPrivilegesRequest(
Sets.newHashSet("monitor", "manage"),
Sets.newHashSet(
IndicesPrivileges.builder().indices("logstash-2018-10-05").privileges("read", "write").build(),
IndicesPrivileges.builder().indices("logstash-2018-*").privileges("read").build()
),
null
);
//end::has-privileges-request
//tag::has-privileges-execute
HasPrivilegesResponse response = client.security().hasPrivileges(request, RequestOptions.DEFAULT);
//end::has-privileges-execute
//tag::has-privileges-response
boolean hasMonitor = response.hasClusterPrivilege("monitor"); // <1>
boolean hasWrite = response.hasIndexPrivilege("logstash-2018-10-05", "write"); // <2>
boolean hasRead = response.hasIndexPrivilege("logstash-2018-*", "read"); // <3>
//end::has-privileges-response
assertThat(response.getUsername(), is("test_user"));
assertThat(response.hasAllRequested(), is(true));
assertThat(hasMonitor, is(true));
assertThat(hasWrite, is(true));
assertThat(hasRead, is(true));
assertThat(response.getApplicationPrivileges().entrySet(), emptyIterable());
}
{
HasPrivilegesRequest request = new HasPrivilegesRequest(Collections.singleton("monitor"),null,null);
// tag::has-privileges-execute-listener
ActionListener<HasPrivilegesResponse> listener = new ActionListener<HasPrivilegesResponse>() {
@Override
public void onResponse(HasPrivilegesResponse response) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::has-privileges-execute-listener
// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);
// tag::has-privileges-execute-async
client.security().hasPrivilegesAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::has-privileges-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
public void testClearRealmCache() throws Exception {
RestHighLevelClient client = highLevelClient();
{

View File

@ -0,0 +1,111 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.security;
import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.elasticsearch.test.XContentTestUtils;
import org.hamcrest.Matchers;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
public class HasPrivilegesRequestTests extends ESTestCase {
public void testToXContent() throws IOException {
final HasPrivilegesRequest request = new HasPrivilegesRequest(
new LinkedHashSet<>(Arrays.asList("monitor", "manage_watcher", "manage_ml")),
new LinkedHashSet<>(Arrays.asList(
IndicesPrivileges.builder().indices("index-001", "index-002").privileges("all").build(),
IndicesPrivileges.builder().indices("index-003").privileges("read").build()
)),
new LinkedHashSet<>(Arrays.asList(
new ApplicationResourcePrivileges("myapp", Arrays.asList("read", "write"), Arrays.asList("*")),
new ApplicationResourcePrivileges("myapp", Arrays.asList("admin"), Arrays.asList("/data/*"))
))
);
String json = Strings.toString(request);
final Map<String, Object> parsed = XContentHelper.convertToMap(XContentType.JSON.xContent(), json, false);
final Map<String, Object> expected = XContentHelper.convertToMap(XContentType.JSON.xContent(), "{" +
" \"cluster\":[\"monitor\",\"manage_watcher\",\"manage_ml\"]," +
" \"index\":[{" +
" \"names\":[\"index-001\",\"index-002\"]," +
" \"privileges\":[\"all\"]" +
" },{" +
" \"names\":[\"index-003\"]," +
" \"privileges\":[\"read\"]" +
" }]," +
" \"application\":[{" +
" \"application\":\"myapp\"," +
" \"privileges\":[\"read\",\"write\"]," +
" \"resources\":[\"*\"]" +
" },{" +
" \"application\":\"myapp\"," +
" \"privileges\":[\"admin\"]," +
" \"resources\":[\"/data/*\"]" +
" }]" +
"}", false);
assertThat(XContentTestUtils.differenceBetweenMapsIgnoringArrayOrder(parsed, expected), Matchers.nullValue());
}
public void testEqualsAndHashCode() {
final Set<String> cluster = Sets.newHashSet(randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 8)));
final Set<IndicesPrivileges> indices = Sets.newHashSet(randomArray(1, 5, IndicesPrivileges[]::new,
() -> IndicesPrivileges.builder()
.indices(generateRandomStringArray(5, 12, false, false))
.privileges(generateRandomStringArray(3, 8, false, false))
.build()));
final Set<ApplicationResourcePrivileges> application = Sets.newHashSet(randomArray(1, 5, ApplicationResourcePrivileges[]::new,
() -> new ApplicationResourcePrivileges(
randomAlphaOfLengthBetween(5, 12),
Sets.newHashSet(generateRandomStringArray(3, 8, false, false)),
Sets.newHashSet(generateRandomStringArray(2, 6, false, false))
)));
final HasPrivilegesRequest request = new HasPrivilegesRequest(cluster, indices, application);
EqualsHashCodeTestUtils.checkEqualsAndHashCode(request, this::copy, this::mutate);
}
private HasPrivilegesRequest copy(HasPrivilegesRequest request) {
return new HasPrivilegesRequest(request.getClusterPrivileges(), request.getIndexPrivileges(), request.getApplicationPrivileges());
}
private HasPrivilegesRequest mutate(HasPrivilegesRequest request) {
switch (randomIntBetween(1, 3)) {
case 1:
return new HasPrivilegesRequest(null, request.getIndexPrivileges(), request.getApplicationPrivileges());
case 2:
return new HasPrivilegesRequest(request.getClusterPrivileges(), null, request.getApplicationPrivileges());
case 3:
return new HasPrivilegesRequest(request.getClusterPrivileges(), request.getIndexPrivileges(), null);
}
throw new IllegalStateException("The universe is broken (or the RNG is)");
}
}

View File

@ -0,0 +1,262 @@
/*
* 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.collect.MapBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.hamcrest.Matchers;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static java.util.Collections.emptyMap;
public class HasPrivilegesResponseTests extends ESTestCase {
public void testParseValidResponse() throws IOException {
String json = "{" +
" \"username\": \"namor\"," +
" \"has_all_requested\": false," +
" \"cluster\" : {" +
" \"manage\" : false," +
" \"monitor\" : true" +
" }," +
" \"index\" : {" +
" \"index-01\": {" +
" \"read\" : true," +
" \"write\" : false" +
" }," +
" \"index-02\": {" +
" \"read\" : true," +
" \"write\" : true" +
" }," +
" \"index-03\": {" +
" \"read\" : false," +
" \"write\" : false" +
" }" +
" }," +
" \"application\" : {" +
" \"app01\" : {" +
" \"/object/1\" : {" +
" \"read\" : true," +
" \"write\" : false" +
" }," +
" \"/object/2\" : {" +
" \"read\" : true," +
" \"write\" : true" +
" }" +
" }," +
" \"app02\" : {" +
" \"/object/1\" : {" +
" \"read\" : false," +
" \"write\" : false" +
" }," +
" \"/object/3\" : {" +
" \"read\" : false," +
" \"write\" : true" +
" }" +
" }" +
" }" +
"}";
final XContentParser parser = createParser(XContentType.JSON.xContent(), json);
HasPrivilegesResponse response = HasPrivilegesResponse.fromXContent(parser);
assertThat(response.getUsername(), Matchers.equalTo("namor"));
assertThat(response.hasAllRequested(), Matchers.equalTo(false));
assertThat(response.getClusterPrivileges().keySet(), Matchers.containsInAnyOrder("monitor", "manage"));
assertThat(response.hasClusterPrivilege("monitor"), Matchers.equalTo(true));
assertThat(response.hasClusterPrivilege("manage"), Matchers.equalTo(false));
assertThat(response.getIndexPrivileges().keySet(), Matchers.containsInAnyOrder("index-01", "index-02", "index-03"));
assertThat(response.hasIndexPrivilege("index-01", "read"), Matchers.equalTo(true));
assertThat(response.hasIndexPrivilege("index-01", "write"), Matchers.equalTo(false));
assertThat(response.hasIndexPrivilege("index-02", "read"), Matchers.equalTo(true));
assertThat(response.hasIndexPrivilege("index-02", "write"), Matchers.equalTo(true));
assertThat(response.hasIndexPrivilege("index-03", "read"), Matchers.equalTo(false));
assertThat(response.hasIndexPrivilege("index-03", "write"), Matchers.equalTo(false));
assertThat(response.getApplicationPrivileges().keySet(), Matchers.containsInAnyOrder("app01", "app02"));
assertThat(response.hasApplicationPrivilege("app01", "/object/1", "read"), Matchers.equalTo(true));
assertThat(response.hasApplicationPrivilege("app01", "/object/1", "write"), Matchers.equalTo(false));
assertThat(response.hasApplicationPrivilege("app01", "/object/2", "read"), Matchers.equalTo(true));
assertThat(response.hasApplicationPrivilege("app01", "/object/2", "write"), Matchers.equalTo(true));
assertThat(response.hasApplicationPrivilege("app02", "/object/1", "read"), Matchers.equalTo(false));
assertThat(response.hasApplicationPrivilege("app02", "/object/1", "write"), Matchers.equalTo(false));
assertThat(response.hasApplicationPrivilege("app02", "/object/3", "read"), Matchers.equalTo(false));
assertThat(response.hasApplicationPrivilege("app02", "/object/3", "write"), Matchers.equalTo(true));
}
public void testHasClusterPrivilege() {
final Map<String, Boolean> cluster = MapBuilder.<String, Boolean>newMapBuilder()
.put("a", true)
.put("b", false)
.put("c", false)
.put("d", true)
.map();
final HasPrivilegesResponse response = new HasPrivilegesResponse("x", false, cluster, emptyMap(), emptyMap());
assertThat(response.hasClusterPrivilege("a"), Matchers.is(true));
assertThat(response.hasClusterPrivilege("b"), Matchers.is(false));
assertThat(response.hasClusterPrivilege("c"), Matchers.is(false));
assertThat(response.hasClusterPrivilege("d"), Matchers.is(true));
final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> response.hasClusterPrivilege("e"));
assertThat(iae.getMessage(), Matchers.containsString("[e]"));
assertThat(iae.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("cluster privilege"));
}
public void testHasIndexPrivilege() {
final Map<String, Map<String, Boolean>> index = MapBuilder.<String, Map<String, Boolean>>newMapBuilder()
.put("i1", Collections.singletonMap("read", true))
.put("i2", Collections.singletonMap("read", false))
.put("i3", MapBuilder.<String, Boolean>newMapBuilder().put("read", true).put("write", true).map())
.put("i4", MapBuilder.<String, Boolean>newMapBuilder().put("read", true).put("write", false).map())
.put("i*", MapBuilder.<String, Boolean>newMapBuilder().put("read", false).put("write", false).map())
.map();
final HasPrivilegesResponse response = new HasPrivilegesResponse("x", false, emptyMap(), index, emptyMap());
assertThat(response.hasIndexPrivilege("i1", "read"), Matchers.is(true));
assertThat(response.hasIndexPrivilege("i2", "read"), Matchers.is(false));
assertThat(response.hasIndexPrivilege("i3", "read"), Matchers.is(true));
assertThat(response.hasIndexPrivilege("i3", "write"), Matchers.is(true));
assertThat(response.hasIndexPrivilege("i4", "read"), Matchers.is(true));
assertThat(response.hasIndexPrivilege("i4", "write"), Matchers.is(false));
assertThat(response.hasIndexPrivilege("i*", "read"), Matchers.is(false));
assertThat(response.hasIndexPrivilege("i*", "write"), Matchers.is(false));
final IllegalArgumentException iae1 = expectThrows(IllegalArgumentException.class, () -> response.hasIndexPrivilege("i0", "read"));
assertThat(iae1.getMessage(), Matchers.containsString("index [i0]"));
final IllegalArgumentException iae2 = expectThrows(IllegalArgumentException.class, () -> response.hasIndexPrivilege("i1", "write"));
assertThat(iae2.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("privilege [write]"));
assertThat(iae2.getMessage(), Matchers.containsString("index [i1]"));
}
public void testHasApplicationPrivilege() {
final Map<String, Map<String, Boolean>> app1 = MapBuilder.<String, Map<String, Boolean>>newMapBuilder()
.put("/data/1", Collections.singletonMap("read", true))
.put("/data/2", Collections.singletonMap("read", false))
.put("/data/3", MapBuilder.<String, Boolean>newMapBuilder().put("read", true).put("write", true).map())
.put("/data/4", MapBuilder.<String, Boolean>newMapBuilder().put("read", true).put("write", false).map())
.map();
final Map<String, Map<String, Boolean>> app2 = MapBuilder.<String, Map<String, Boolean>>newMapBuilder()
.put("/action/1", Collections.singletonMap("execute", true))
.put("/action/*", Collections.singletonMap("execute", false))
.map();
Map<String, Map<String, Map<String, Boolean>>> appPrivileges = new HashMap<>();
appPrivileges.put("a1", app1);
appPrivileges.put("a2", app2);
final HasPrivilegesResponse response = new HasPrivilegesResponse("x", false, emptyMap(), emptyMap(), appPrivileges);
assertThat(response.hasApplicationPrivilege("a1", "/data/1", "read"), Matchers.is(true));
assertThat(response.hasApplicationPrivilege("a1", "/data/2", "read"), Matchers.is(false));
assertThat(response.hasApplicationPrivilege("a1", "/data/3", "read"), Matchers.is(true));
assertThat(response.hasApplicationPrivilege("a1", "/data/3", "write"), Matchers.is(true));
assertThat(response.hasApplicationPrivilege("a1", "/data/4", "read"), Matchers.is(true));
assertThat(response.hasApplicationPrivilege("a1", "/data/4", "write"), Matchers.is(false));
assertThat(response.hasApplicationPrivilege("a2", "/action/1", "execute"), Matchers.is(true));
assertThat(response.hasApplicationPrivilege("a2", "/action/*", "execute"), Matchers.is(false));
final IllegalArgumentException iae1 = expectThrows(IllegalArgumentException.class,
() -> response.hasApplicationPrivilege("a0", "/data/1", "read"));
assertThat(iae1.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("application [a0]"));
final IllegalArgumentException iae2 = expectThrows(IllegalArgumentException.class,
() -> response.hasApplicationPrivilege("a1", "/data/0", "read"));
assertThat(iae2.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("application [a1]"));
assertThat(iae2.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("resource [/data/0]"));
final IllegalArgumentException iae3 = expectThrows(IllegalArgumentException.class,
() -> response.hasApplicationPrivilege("a1", "/action/1", "execute"));
assertThat(iae3.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("application [a1]"));
assertThat(iae3.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("resource [/action/1]"));
final IllegalArgumentException iae4 = expectThrows(IllegalArgumentException.class,
() -> response.hasApplicationPrivilege("a1", "/data/1", "write"));
assertThat(iae4.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("application [a1]"));
assertThat(iae4.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("resource [/data/1]"));
assertThat(iae4.getMessage().toLowerCase(Locale.ROOT), Matchers.containsString("privilege [write]"));
}
public void testEqualsAndHashCode() {
final HasPrivilegesResponse response = randomResponse();
EqualsHashCodeTestUtils.checkEqualsAndHashCode(response, this::copy, this::mutate);
}
private HasPrivilegesResponse copy(HasPrivilegesResponse response) {
return new HasPrivilegesResponse(response.getUsername(),
response.hasAllRequested(),
response.getClusterPrivileges(),
response.getIndexPrivileges(),
response.getApplicationPrivileges());
}
private HasPrivilegesResponse mutate(HasPrivilegesResponse request) {
switch (randomIntBetween(1, 5)) {
case 1:
return new HasPrivilegesResponse("_" + request.getUsername(), request.hasAllRequested(),
request.getClusterPrivileges(), request.getIndexPrivileges(), request.getApplicationPrivileges());
case 2:
return new HasPrivilegesResponse(request.getUsername(), request.hasAllRequested() == false,
request.getClusterPrivileges(), request.getIndexPrivileges(), request.getApplicationPrivileges());
case 3:
return new HasPrivilegesResponse(request.getUsername(), request.hasAllRequested(),
emptyMap(), request.getIndexPrivileges(), request.getApplicationPrivileges());
case 4:
return new HasPrivilegesResponse(request.getUsername(), request.hasAllRequested(),
request.getClusterPrivileges(), emptyMap(), request.getApplicationPrivileges());
case 5:
return new HasPrivilegesResponse(request.getUsername(), request.hasAllRequested(),
request.getClusterPrivileges(), request.getIndexPrivileges(), emptyMap());
}
throw new IllegalStateException("The universe is broken (or the RNG is)");
}
private HasPrivilegesResponse randomResponse() {
final Map<String, Boolean> cluster = randomPrivilegeMap();
final Map<String, Map<String, Boolean>> index = randomResourceMap();
final Map<String, Map<String, Map<String, Boolean>>> application = new HashMap<>();
for (String app : randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(3, 6).toLowerCase(Locale.ROOT))) {
application.put(app, randomResourceMap());
}
return new HasPrivilegesResponse(randomAlphaOfLengthBetween(3, 8), randomBoolean(), cluster, index, application);
}
private Map<String, Map<String, Boolean>> randomResourceMap() {
final Map<String, Map<String, Boolean>> resource = new HashMap<>();
for (String res : randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(5, 8))) {
resource.put(res, randomPrivilegeMap());
}
return resource;
}
private Map<String, Boolean> randomPrivilegeMap() {
final Map<String, Boolean> map = new HashMap<>();
for (String privilege : randomArray(1, 6, String[]::new, () -> randomAlphaOfLengthBetween(3, 12))) {
map.put(privilege, randomBoolean());
}
return map;
}
}

View File

@ -0,0 +1,86 @@
--
:api: has-privileges
:request: HasPrivilegesRequest
:response: HasPrivilegesResponse
--
[id="{upid}-{api}"]
=== Has Privileges API
[id="{upid}-{api}-request"]
==== Has Privileges Request
The +{request}+ supports checking for any or all of the following privilege types:
* Cluster Privileges
* Index Privileges
* Application Privileges
Privileges types that you do not wish to check my be passed in as +null+, but as least
one privilege must be specified.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request]
--------------------------------------------------
include::../execution.asciidoc[]
[id="{upid}-{api}-response"]
==== Has Privileges Response
The returned +{response}+ contains the following properties
`username`::
The username (userid) of the current user (for whom the "has privileges"
check was executed)
`hasAllRequested`::
`true` if the user has all of the privileges that were specified in the
+{request}+. Otherwise `false`.
`clusterPrivileges`::
A `Map<String,Boolean>` where each key is the name of one of the cluster
privileges specified in the request, and the value is `true` if the user
has that privilege, and `false` otherwise.
+
The method `hasClusterPrivilege` can be used to retrieve this information
in a more fluent manner. This method throws an `IllegalArgumentException`
if the privilege was not included in the response (which will be the case
if the privilege was not part of the request).
`indexPrivileges`::
A `Map<String, Map<String, Boolean>>` where each key is the name of an
index (as specified in the +{request}+) and the value is a `Map` from
privilege name to a `Boolean`. The `Boolean` value is `true` if the user
has that privilege on that index, and `false` otherwise.
+
The method `hasIndexPrivilege` can be used to retrieve this information
in a more fluent manner. This method throws an `IllegalArgumentException`
if the privilege was not included in the response (which will be the case
if the privilege was not part of the request).
`applicationPrivileges`::
A `Map<String, Map<String, Map<String, Boolean>>>>` where each key is the
name of an application (as specified in the +{request}+).
For each application, the value is a `Map` keyed by resource name, with
each value being another `Map` from privilege name to a `Boolean`.
The `Boolean` value is `true` if the user has that privilege on that
resource for that application, and `false` otherwise.
+
The method `hasApplicationPrivilege` can be used to retrieve this
information in a more fluent manner. This method throws an
`IllegalArgumentException` if the privilege was not included in the
response (which will be the case if the privilege was not part of the
request).
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
<1> `hasMonitor` will be `true` if the user has the `"monitor"`
cluster privilege.
<2> `hasWrite` will be `true` if the user has the `"write"`
privilege on the `"logstash-2018-10-05"` index.
<3> `hasRead` will be `true` if the user has the `"read"`
privilege on all possible indices that would match
the `"logstash-2018-*"` pattern.

View File

@ -351,6 +351,7 @@ The Java High Level REST Client supports the following Security APIs:
* <<{upid}-clear-roles-cache>>
* <<{upid}-clear-realm-cache>>
* <<{upid}-authenticate>>
* <<{upid}-has-privileges>>
* <<java-rest-high-security-get-certificates>>
* <<java-rest-high-security-put-role-mapping>>
* <<java-rest-high-security-get-role-mappings>>
@ -368,6 +369,7 @@ include::security/delete-privileges.asciidoc[]
include::security/clear-roles-cache.asciidoc[]
include::security/clear-realm-cache.asciidoc[]
include::security/authenticate.asciidoc[]
include::security/has-privileges.asciidoc[]
include::security/get-certificates.asciidoc[]
include::security/put-role-mapping.asciidoc[]
include::security/get-role-mappings.asciidoc[]