HLRC: Implement get-user-privileges API (#36292)

This adds the _security/user/_privileges API to the High
Level Rest Client.

This also makes some changes to the Java model for the Role APIs
in order to better accommodate the GetPrivileges API
This commit is contained in:
Tim Vernum 2018-12-12 15:12:49 +11:00 committed by GitHub
parent 03daad9812
commit 143f151185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 958 additions and 150 deletions

View File

@ -48,6 +48,8 @@ import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.GetRolesResponse;
import org.elasticsearch.client.security.GetSslCertificatesRequest;
import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.GetUserPrivilegesRequest;
import org.elasticsearch.client.security.GetUserPrivilegesResponse;
import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
@ -311,6 +313,25 @@ public final class SecurityClient {
HasPrivilegesResponse::fromXContent, listener, emptySet());
}
/**
* Retrieve the set of effective privileges held by the current user.
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
*/
public GetUserPrivilegesResponse getUserPrivileges(RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(GetUserPrivilegesRequest.INSTANCE, GetUserPrivilegesRequest::getRequest,
options, GetUserPrivilegesResponse::fromXContent, emptySet());
}
/**
* Asynchronously retrieve the set of effective privileges held by the current user.
* @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 getUserPrivilegesAsync(RequestOptions options, ActionListener<GetUserPrivilegesResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(GetUserPrivilegesRequest.INSTANCE, GetUserPrivilegesRequest::getRequest,
options, GetUserPrivilegesResponse::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">
@ -697,5 +718,4 @@ public final class SecurityClient {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::deletePrivileges, options,
DeletePrivilegesResponse::fromXContent, listener, singleton(404));
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.apache.http.client.methods.HttpGet;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Validatable;
/**
* A request object for the {@link org.elasticsearch.client.SecurityClient#getUserPrivileges(RequestOptions)} API.
* This request takes no parameters, and has a singleton {@link #INSTANCE}.
*/
public class GetUserPrivilegesRequest implements Validatable {
public static final GetUserPrivilegesRequest INSTANCE = new GetUserPrivilegesRequest();
private GetUserPrivilegesRequest() {
}
public Request getRequest() {
return new Request(HttpGet.METHOD_NAME, "/_security/user/_privileges");
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.RequestOptions;
import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
import org.elasticsearch.client.security.user.privileges.GlobalPrivileges;
import org.elasticsearch.client.security.user.privileges.UserIndicesPrivileges;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/**
* The response for the {@link org.elasticsearch.client.SecurityClient#getUserPrivileges(RequestOptions)} API.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user-privileges.html">the API docs</a>
*/
public class GetUserPrivilegesResponse {
private static final ConstructingObjectParser<GetUserPrivilegesResponse, Void> PARSER = new ConstructingObjectParser<>(
"get_user_privileges_response", true, GetUserPrivilegesResponse::buildResponseFromParserArgs);
@SuppressWarnings("unchecked")
private static GetUserPrivilegesResponse buildResponseFromParserArgs(Object[] args) {
return new GetUserPrivilegesResponse(
(Collection<String>) args[0],
(Collection<GlobalPrivileges>) args[1],
(Collection<UserIndicesPrivileges>) args[2],
(Collection<ApplicationResourcePrivileges>) args[3],
(Collection<String>) args[4]
);
}
static {
PARSER.declareStringArray(constructorArg(), new ParseField("cluster"));
PARSER.declareObjectArray(constructorArg(), (parser, ignore) -> GlobalPrivileges.fromXContent(parser),
new ParseField("global"));
PARSER.declareObjectArray(constructorArg(), (parser, ignore) -> UserIndicesPrivileges.fromXContent(parser),
new ParseField("indices"));
PARSER.declareObjectArray(constructorArg(), (parser, ignore) -> ApplicationResourcePrivileges.fromXContent(parser),
new ParseField("applications"));
PARSER.declareStringArray(constructorArg(), new ParseField("run_as"));
}
public static GetUserPrivilegesResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
private Set<String> clusterPrivileges;
private Set<GlobalPrivileges> globalPrivileges;
private Set<UserIndicesPrivileges> indicesPrivileges;
private Set<ApplicationResourcePrivileges> applicationPrivileges;
private Set<String> runAsPrivilege;
public GetUserPrivilegesResponse(Collection<String> clusterPrivileges, Collection<GlobalPrivileges> globalPrivileges,
Collection<UserIndicesPrivileges> indicesPrivileges,
Collection<ApplicationResourcePrivileges> applicationPrivileges, Collection<String> runAsPrivilege) {
this.clusterPrivileges = Collections.unmodifiableSet(new LinkedHashSet<>(clusterPrivileges));
this.globalPrivileges = Collections.unmodifiableSet(new LinkedHashSet<>(globalPrivileges));
this.indicesPrivileges = Collections.unmodifiableSet(new LinkedHashSet<>(indicesPrivileges));
this.applicationPrivileges = Collections.unmodifiableSet(new LinkedHashSet<>(applicationPrivileges));
this.runAsPrivilege = Collections.unmodifiableSet(new LinkedHashSet<>(runAsPrivilege));
}
public Set<String> getClusterPrivileges() {
return clusterPrivileges;
}
public Set<GlobalPrivileges> getGlobalPrivileges() {
return globalPrivileges;
}
public Set<UserIndicesPrivileges> getIndicesPrivileges() {
return indicesPrivileges;
}
public Set<ApplicationResourcePrivileges> getApplicationPrivileges() {
return applicationPrivileges;
}
public Set<String> getRunAsPrivilege() {
return runAsPrivilege;
}
@Override
public String toString() {
return "GetUserPrivilegesResponse{" +
"clusterPrivileges=" + clusterPrivileges +
", globalPrivileges=" + globalPrivileges +
", indicesPrivileges=" + indicesPrivileges +
", applicationPrivileges=" + applicationPrivileges +
", runAsPrivilege=" + runAsPrivilege +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final GetUserPrivilegesResponse that = (GetUserPrivilegesResponse) o;
return Objects.equals(this.clusterPrivileges, that.clusterPrivileges) &&
Objects.equals(this.globalPrivileges, that.globalPrivileges) &&
Objects.equals(this.indicesPrivileges, that.indicesPrivileges) &&
Objects.equals(this.applicationPrivileges, that.applicationPrivileges) &&
Objects.equals(this.runAsPrivilege, that.runAsPrivilege);
}
@Override
public int hashCode() {
return Objects.hash(clusterPrivileges, globalPrivileges, indicesPrivileges, applicationPrivileges, runAsPrivilege);
}
}

View File

@ -74,14 +74,14 @@ public final class PutRoleRequest implements Validatable, ToXContentObject {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (role.getApplicationResourcePrivileges() != null) {
builder.field(Role.APPLICATIONS.getPreferredName(), role.getApplicationResourcePrivileges());
if (role.getApplicationPrivileges() != null) {
builder.field(Role.APPLICATIONS.getPreferredName(), role.getApplicationPrivileges());
}
if (role.getClusterPrivileges() != null) {
builder.field(Role.CLUSTER.getPreferredName(), role.getClusterPrivileges());
}
if (role.getGlobalApplicationPrivileges() != null) {
builder.field(Role.GLOBAL.getPreferredName(), role.getGlobalApplicationPrivileges());
if (role.getGlobalPrivileges() != null) {
builder.field(Role.GLOBAL.getPreferredName(), role.getGlobalPrivileges());
}
if (role.getIndicesPrivileges() != null) {
builder.field(Role.INDICES.getPreferredName(), role.getIndicesPrivileges());

View File

@ -0,0 +1,199 @@
/*
* 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.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
public abstract class AbstractIndicesPrivileges {
static final ParseField NAMES = new ParseField("names");
static final ParseField PRIVILEGES = new ParseField("privileges");
static final ParseField FIELD_PERMISSIONS = new ParseField("field_security");
static final ParseField QUERY = new ParseField("query");
protected final Set<String> indices;
protected final Set<String> privileges;
AbstractIndicesPrivileges(Collection<String> indices, Collection<String> privileges) {
if (null == indices || indices.isEmpty()) {
throw new IllegalArgumentException("indices privileges must refer to at least one index name or index name pattern");
}
if (null == privileges || privileges.isEmpty()) {
throw new IllegalArgumentException("indices privileges must define at least one privilege");
}
this.indices = Collections.unmodifiableSet(new HashSet<>(indices));
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
}
/**
* The indices names covered by the privileges.
*/
public Set<String> getIndices() {
return this.indices;
}
/**
* The privileges acting over indices. There is a canonical predefined set of
* such privileges, but the {@code String} datatype allows for flexibility in defining
* finer grained privileges.
*/
public Set<String> getPrivileges() {
return this.privileges;
}
/**
* If {@code true} some documents might not be visible. Only the documents
* matching {@code query} will be readable.
*/
public abstract boolean isUsingDocumentLevelSecurity();
/**
* If {@code true} some document fields might not be visible.
*/
public abstract boolean isUsingFieldLevelSecurity();
public static class FieldSecurity implements ToXContentObject {
static final ParseField GRANT_FIELDS = new ParseField("grant");
static final ParseField EXCEPT_FIELDS = new ParseField("except");
private static final ConstructingObjectParser<IndicesPrivileges.FieldSecurity, Void> PARSER = new ConstructingObjectParser<>(
FIELD_PERMISSIONS.getPreferredName(), true, FieldSecurity::buildObjectFromParserArgs);
@SuppressWarnings("unchecked")
private static FieldSecurity buildObjectFromParserArgs(Object[] args) {
return new FieldSecurity(
(Collection<String>) args[0],
(Collection<String>) args[1]
);
}
static {
PARSER.declareStringArray(optionalConstructorArg(), GRANT_FIELDS);
PARSER.declareStringArray(optionalConstructorArg(), EXCEPT_FIELDS);
}
static FieldSecurity parse(XContentParser parser, Void context) throws IOException {
return PARSER.parse(parser, context);
}
// null or singleton '*' means all fields are granted, empty means no fields are granted
private final Set<String> grantedFields;
// null or empty means no fields are denied
private final Set<String> deniedFields;
FieldSecurity(Collection<String> grantedFields, Collection<String> deniedFields) {
// unspecified granted fields means no restriction
this.grantedFields = grantedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(grantedFields));
// unspecified denied fields means no restriction
this.deniedFields = deniedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(deniedFields));
}
/**
* The document fields that can be read or queried. Can be null, in this case
* all the document's fields are granted access to. Can also be empty, in which
* case no fields are granted access to.
*/
public Set<String> getGrantedFields() {
return grantedFields;
}
/**
* The document fields that cannot be accessed or queried. Can be null or empty,
* in which case no fields are denied.
*/
public Set<String> getDeniedFields() {
return deniedFields;
}
public boolean isUsingFieldLevelSecurity() {
return limitsGrantedFields() || hasDeniedFields();
}
private boolean hasDeniedFields() {
return deniedFields != null && false == deniedFields.isEmpty();
}
private boolean limitsGrantedFields() {
// we treat just '*' as no FLS since that's what the UI defaults to
if (grantedFields == null || (grantedFields.size() == 1 && grantedFields.iterator().next().equals("*"))) {
return false;
}
return true;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (grantedFields == null) {
// The role parser will reject a field_security object that doesn't have a "granted" field
builder.field(GRANT_FIELDS.getPreferredName(), Collections.singletonList("*"));
} else {
builder.field(GRANT_FIELDS.getPreferredName(), grantedFields);
}
if (deniedFields != null) {
builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields);
}
return builder.endObject();
}
@Override
public String toString() {
try {
return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
} catch (IOException e) {
throw new UncheckedIOException("Unexpected", e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final FieldSecurity that = (FieldSecurity) o;
return Objects.equals(this.grantedFields, that.grantedFields) &&
Objects.equals(this.deniedFields, that.deniedFields);
}
@Override
public int hashCode() {
return Objects.hash(grantedFields, deniedFields);
}
}
}

View File

@ -20,22 +20,17 @@
package org.elasticsearch.client.security.user.privileges;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
@ -47,104 +42,44 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optiona
* This also encapsulates field and document level security privileges. These
* allow to control what fields or documents are readable or queryable.
*/
public final class IndicesPrivileges implements ToXContentObject {
public static final ParseField NAMES = new ParseField("names");
public static final ParseField PRIVILEGES = new ParseField("privileges");
public static final ParseField FIELD_PERMISSIONS = new ParseField("field_security");
public static final ParseField GRANT_FIELDS = new ParseField("grant");
public static final ParseField EXCEPT_FIELDS = new ParseField("except");
public static final ParseField QUERY = new ParseField("query");
public final class IndicesPrivileges extends AbstractIndicesPrivileges implements ToXContentObject {
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<IndicesPrivileges, Void> PARSER =
new ConstructingObjectParser<>("indices_privileges", false, constructorObjects -> {
int i = 0;
final Collection<String> indices = (Collection<String>) constructorObjects[i++];
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
final Tuple<Collection<String>, Collection<String>> fields =
(Tuple<Collection<String>, Collection<String>>) constructorObjects[i++];
final Collection<String> grantFields = fields != null ? fields.v1() : null;
final Collection<String> exceptFields = fields != null ? fields.v2() : null;
final String query = (String) constructorObjects[i];
return new IndicesPrivileges(indices, privileges, grantFields, exceptFields, query);
});
int i = 0;
final Collection<String> indices = (Collection<String>) constructorObjects[i++];
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
final FieldSecurity fields = (FieldSecurity) constructorObjects[i++];
final String query = (String) constructorObjects[i];
return new IndicesPrivileges(indices, privileges, fields, query);
});
static {
@SuppressWarnings("unchecked")
final ConstructingObjectParser<Tuple<Collection<String>, Collection<String>>, Void> fls_parser =
new ConstructingObjectParser<>( "field_level_parser", false, constructorObjects -> {
int i = 0;
final Collection<String> grantFields = (Collection<String>) constructorObjects[i++];
final Collection<String> exceptFields = (Collection<String>) constructorObjects[i];
return new Tuple<>(grantFields, exceptFields);
});
fls_parser.declareStringArray(optionalConstructorArg(), GRANT_FIELDS);
fls_parser.declareStringArray(optionalConstructorArg(), EXCEPT_FIELDS);
PARSER.declareStringArray(constructorArg(), NAMES);
PARSER.declareStringArray(constructorArg(), PRIVILEGES);
PARSER.declareObject(optionalConstructorArg(), fls_parser, FIELD_PERMISSIONS);
PARSER.declareObject(optionalConstructorArg(), FieldSecurity::parse, FIELD_PERMISSIONS);
PARSER.declareStringOrNull(optionalConstructorArg(), QUERY);
}
private final Set<String> indices;
private final Set<String> privileges;
// null or singleton '*' means all fields are granted, empty means no fields are granted
private final @Nullable Set<String> grantedFields;
// null or empty means no fields are denied
private final @Nullable Set<String> deniedFields;
private final FieldSecurity fieldSecurity;
// missing query means all documents, i.e. no restrictions
private final @Nullable String query;
private IndicesPrivileges(Collection<String> indices, Collection<String> privileges, @Nullable Collection<String> grantedFields,
@Nullable Collection<String> deniedFields, @Nullable String query) {
if (null == indices || indices.isEmpty()) {
throw new IllegalArgumentException("indices privileges must refer to at least one index name or index name pattern");
}
if (null == privileges || privileges.isEmpty()) {
throw new IllegalArgumentException("indices privileges must define at least one privilege");
}
this.indices = Collections.unmodifiableSet(new HashSet<>(indices));
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
// unspecified granted fields means no restriction
this.grantedFields = grantedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(grantedFields));
// unspecified denied fields means no restriction
this.deniedFields = deniedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(deniedFields));
private IndicesPrivileges(Collection<String> indices, Collection<String> privileges, @Nullable FieldSecurity fieldSecurity,
@Nullable String query) {
super(indices, privileges);
this.fieldSecurity = fieldSecurity;
this.query = query;
}
/**
* The indices names covered by the privileges.
* The combination of the {@link FieldSecurity#getGrantedFields() granted} and
* {@link FieldSecurity#getDeniedFields() denied} document fields.
* May be null, in which case no field level security is applicable, and all the document's fields are granted access to.
*/
public Set<String> getIndices() {
return this.indices;
}
/**
* The privileges acting over indices. There is a canonical predefined set of
* such privileges, but the {@code String} datatype allows for flexibility in defining
* finer grained privileges.
*/
public Set<String> getPrivileges() {
return this.privileges;
}
/**
* The document fields that can be read or queried. Can be null, in this case
* all the document's fields are granted access to. Can also be empty, in which
* case no fields are granted access to.
*/
public @Nullable Set<String> getGrantedFields() {
return this.grantedFields;
}
/**
* The document fields that cannot be accessed or queried. Can be null or empty,
* in which case no fields are denied.
*/
public @Nullable Set<String> getDeniedFields() {
return this.deniedFields;
public FieldSecurity getFieldSecurity() {
return fieldSecurity;
}
/**
@ -159,6 +94,7 @@ public final class IndicesPrivileges implements ToXContentObject {
* If {@code true} some documents might not be visible. Only the documents
* matching {@code query} will be readable.
*/
@Override
public boolean isUsingDocumentLevelSecurity() {
return query != null;
}
@ -166,20 +102,9 @@ public final class IndicesPrivileges implements ToXContentObject {
/**
* If {@code true} some document fields might not be visible.
*/
@Override
public boolean isUsingFieldLevelSecurity() {
return limitsGrantedFields() || hasDeniedFields();
}
private boolean hasDeniedFields() {
return deniedFields != null && false == deniedFields.isEmpty();
}
private boolean limitsGrantedFields() {
// we treat just '*' as no FLS since that's what the UI defaults to
if (grantedFields == null || (grantedFields.size() == 1 && grantedFields.iterator().next().equals("*"))) {
return false;
}
return true;
return fieldSecurity != null && fieldSecurity.isUsingFieldLevelSecurity();
}
@Override
@ -192,15 +117,14 @@ public final class IndicesPrivileges implements ToXContentObject {
}
IndicesPrivileges that = (IndicesPrivileges) o;
return indices.equals(that.indices)
&& privileges.equals(that.privileges)
&& Objects.equals(grantedFields, that.grantedFields)
&& Objects.equals(deniedFields, that.deniedFields)
&& Objects.equals(query, that.query);
&& privileges.equals(that.privileges)
&& Objects.equals(this.fieldSecurity, that.fieldSecurity)
&& Objects.equals(query, that.query);
}
@Override
public int hashCode() {
return Objects.hash(indices, privileges, grantedFields, deniedFields, query);
return Objects.hash(indices, privileges, fieldSecurity, query);
}
@Override
@ -217,15 +141,8 @@ public final class IndicesPrivileges implements ToXContentObject {
builder.startObject();
builder.field(NAMES.getPreferredName(), indices);
builder.field(PRIVILEGES.getPreferredName(), privileges);
if (grantedFields != null || deniedFields != null) {
builder.startObject(FIELD_PERMISSIONS.getPreferredName());
if (grantedFields != null) {
builder.field(GRANT_FIELDS.getPreferredName(), grantedFields);
}
if (deniedFields != null) {
builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields);
}
builder.endObject();
if (fieldSecurity != null) {
builder.field(FIELD_PERMISSIONS.getPreferredName(), fieldSecurity, params);
}
if (isUsingDocumentLevelSecurity()) {
builder.field("query", query);
@ -243,11 +160,16 @@ public final class IndicesPrivileges implements ToXContentObject {
public static final class Builder {
private @Nullable Collection<String> indices = null;
private @Nullable Collection<String> privileges = null;
private @Nullable Collection<String> grantedFields = null;
private @Nullable Collection<String> deniedFields = null;
private @Nullable String query = null;
private @Nullable
Collection<String> indices = null;
private @Nullable
Collection<String> privileges = null;
private @Nullable
Collection<String> grantedFields = null;
private @Nullable
Collection<String> deniedFields = null;
private @Nullable
String query = null;
public Builder() {
}
@ -255,7 +177,7 @@ public final class IndicesPrivileges implements ToXContentObject {
public Builder indices(String... indices) {
return indices(Arrays.asList(Objects.requireNonNull(indices, "indices required")));
}
public Builder indices(Collection<String> indices) {
this.indices = Objects.requireNonNull(indices, "indices required");
return this;
@ -302,7 +224,13 @@ public final class IndicesPrivileges implements ToXContentObject {
}
public IndicesPrivileges build() {
return new IndicesPrivileges(indices, privileges, grantedFields, deniedFields, query);
final FieldSecurity fieldSecurity;
if (grantedFields == null && deniedFields == null) {
fieldSecurity = null;
} else {
fieldSecurity = new FieldSecurity(grantedFields, deniedFields);
}
return new IndicesPrivileges(indices, privileges, fieldSecurity, query);
}
}

View File

@ -85,16 +85,16 @@ public final class Role {
private final String name;
private final Set<String> clusterPrivileges;
private final @Nullable GlobalPrivileges globalApplicationPrivileges;
private final @Nullable GlobalPrivileges globalPrivileges;
private final Set<IndicesPrivileges> indicesPrivileges;
private final Set<ApplicationResourcePrivileges> applicationResourcePrivileges;
private final Set<ApplicationResourcePrivileges> applicationPrivileges;
private final Set<String> runAsPrivilege;
private final Map<String, Object> metadata;
private Role(String name, @Nullable Collection<String> clusterPrivileges,
@Nullable GlobalPrivileges globalApplicationPrivileges,
@Nullable GlobalPrivileges globalPrivileges,
@Nullable Collection<IndicesPrivileges> indicesPrivileges,
@Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges,
@Nullable Collection<ApplicationResourcePrivileges> applicationPrivileges,
@Nullable Collection<String> runAsPrivilege, @Nullable Map<String, Object> metadata) {
if (Strings.hasText(name) == false){
throw new IllegalArgumentException("role name must be provided");
@ -104,13 +104,13 @@ public final class Role {
// no cluster privileges are granted unless otherwise specified
this.clusterPrivileges = Collections
.unmodifiableSet(clusterPrivileges != null ? new HashSet<>(clusterPrivileges) : Collections.emptySet());
this.globalApplicationPrivileges = globalApplicationPrivileges;
this.globalPrivileges = globalPrivileges;
// no indices privileges are granted unless otherwise specified
this.indicesPrivileges = Collections
.unmodifiableSet(indicesPrivileges != null ? new HashSet<>(indicesPrivileges) : Collections.emptySet());
// no application resource privileges are granted unless otherwise specified
this.applicationResourcePrivileges = Collections.unmodifiableSet(
applicationResourcePrivileges != null ? new HashSet<>(applicationResourcePrivileges) : Collections.emptySet());
this.applicationPrivileges = Collections.unmodifiableSet(
applicationPrivileges != null ? new HashSet<>(applicationPrivileges) : Collections.emptySet());
// no run as privileges are granted unless otherwise specified
this.runAsPrivilege = Collections.unmodifiableSet(runAsPrivilege != null ? new HashSet<>(runAsPrivilege) : Collections.emptySet());
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
@ -124,16 +124,16 @@ public final class Role {
return clusterPrivileges;
}
public GlobalPrivileges getGlobalApplicationPrivileges() {
return globalApplicationPrivileges;
public GlobalPrivileges getGlobalPrivileges() {
return globalPrivileges;
}
public Set<IndicesPrivileges> getIndicesPrivileges() {
return indicesPrivileges;
}
public Set<ApplicationResourcePrivileges> getApplicationResourcePrivileges() {
return applicationResourcePrivileges;
public Set<ApplicationResourcePrivileges> getApplicationPrivileges() {
return applicationPrivileges;
}
public Set<String> getRunAsPrivilege() {
@ -151,16 +151,16 @@ public final class Role {
Role that = (Role) o;
return name.equals(that.name)
&& clusterPrivileges.equals(that.clusterPrivileges)
&& Objects.equals(globalApplicationPrivileges, that.globalApplicationPrivileges)
&& Objects.equals(globalPrivileges, that.globalPrivileges)
&& indicesPrivileges.equals(that.indicesPrivileges)
&& applicationResourcePrivileges.equals(that.applicationResourcePrivileges)
&& applicationPrivileges.equals(that.applicationPrivileges)
&& runAsPrivilege.equals(that.runAsPrivilege)
&& metadata.equals(that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
return Objects.hash(name, clusterPrivileges, globalPrivileges, indicesPrivileges, applicationPrivileges,
runAsPrivilege, metadata);
}
@ -173,9 +173,9 @@ public final class Role {
sb.append(clusterPrivileges.toString());
sb.append(", ");
}
if (globalApplicationPrivileges != null) {
sb.append("GlobalApplcationPrivileges=");
sb.append(globalApplicationPrivileges.toString());
if (globalPrivileges != null) {
sb.append("GlobalPrivileges=");
sb.append(globalPrivileges.toString());
sb.append(", ");
}
if (false == indicesPrivileges.isEmpty()) {
@ -183,9 +183,9 @@ public final class Role {
sb.append(indicesPrivileges.toString());
sb.append(", ");
}
if (false == applicationResourcePrivileges.isEmpty()) {
if (false == applicationPrivileges.isEmpty()) {
sb.append("ApplicationPrivileges=");
sb.append(applicationResourcePrivileges.toString());
sb.append(applicationPrivileges.toString());
sb.append(", ");
}
if (false == runAsPrivilege.isEmpty()) {

View File

@ -0,0 +1,135 @@
/*
* 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.client.security.GetUserPrivilegesResponse;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
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 "index" privilege in the {@link GetUserPrivilegesResponse}. This differs from the
* {@link org.elasticsearch.client.security.user.privileges.IndicesPrivileges}" object in a
* {@link org.elasticsearch.client.security.user.privileges.Role}
* as it supports an array value for {@link #getFieldSecurity() field_security} and {@link #getQueries() query}.
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user-privileges.html">the API docs</a>
*/
public class UserIndicesPrivileges extends AbstractIndicesPrivileges {
private final Set<IndicesPrivileges.FieldSecurity> fieldSecurity;
private final Set<String> query;
private static final ConstructingObjectParser<UserIndicesPrivileges, Void> PARSER = new ConstructingObjectParser<>(
"user_indices_privilege", true, UserIndicesPrivileges::buildObjectFromParserArgs);
static {
PARSER.declareStringArray(constructorArg(), IndicesPrivileges.NAMES);
PARSER.declareStringArray(constructorArg(), IndicesPrivileges.PRIVILEGES);
PARSER.declareObjectArray(optionalConstructorArg(), IndicesPrivileges.FieldSecurity::parse, IndicesPrivileges.FIELD_PERMISSIONS);
PARSER.declareStringArray(optionalConstructorArg(), IndicesPrivileges.QUERY);
}
@SuppressWarnings("unchecked")
private static UserIndicesPrivileges buildObjectFromParserArgs(Object[] args) {
return new UserIndicesPrivileges(
(List<String>) args[0],
(List<String>) args[1],
(List<IndicesPrivileges.FieldSecurity>) args[2],
(List<String>) args[3]
);
}
public static UserIndicesPrivileges fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
public UserIndicesPrivileges(Collection<String> indices, Collection<String> privileges,
Collection<IndicesPrivileges.FieldSecurity> fieldSecurity, Collection<String> query) {
super(indices, privileges);
this.fieldSecurity = fieldSecurity == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(fieldSecurity));
this.query = query == null ? Collections.emptySet() : Collections.unmodifiableSet(new HashSet<>(query));
}
public Set<String> getIndices() {
return indices;
}
public Set<String> getPrivileges() {
return privileges;
}
public Set<IndicesPrivileges.FieldSecurity> getFieldSecurity() {
return fieldSecurity;
}
public Set<String> getQueries() {
return query;
}
@Override
public boolean isUsingDocumentLevelSecurity() {
return query.isEmpty() == false;
}
@Override
public boolean isUsingFieldLevelSecurity() {
return fieldSecurity.stream().anyMatch(FieldSecurity::isUsingFieldLevelSecurity);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final UserIndicesPrivileges that = (UserIndicesPrivileges) o;
return Objects.equals(indices, that.indices) &&
Objects.equals(privileges, that.privileges) &&
Objects.equals(fieldSecurity, that.fieldSecurity) &&
Objects.equals(query, that.query);
}
@Override
public int hashCode() {
return Objects.hash(indices, privileges, fieldSecurity, query);
}
@Override
public String toString() {
return "UserIndexPrivilege{" +
"indices=" + indices +
", privileges=" + privileges +
", fieldSecurity=" + fieldSecurity +
", query=" + query +
'}';
}
}

View File

@ -20,7 +20,6 @@
package org.elasticsearch.client;
import com.fasterxml.jackson.core.JsonParseException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
@ -60,6 +59,7 @@ import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -121,6 +121,21 @@ public class RestHighLevelClientTests extends ESTestCase {
private static final ProtocolVersion HTTP_PROTOCOL = new ProtocolVersion("http", 1, 1);
private static final RequestLine REQUEST_LINE = new BasicRequestLine(HttpGet.METHOD_NAME, "/", HTTP_PROTOCOL);
/**
* These APIs do not use a Request object (because they don't have a body, or any request parameters).
* The method naming/parameter assertions use this {@code Set} to determine which rules to apply.
* (This is also used for async variants of these APIs when they exist)
*/
private static final Set<String> APIS_WITHOUT_REQUEST_OBJECT = Sets.newHashSet(
// core
"ping", "info",
// security
"security.get_ssl_certificates", "security.authenticate", "security.get_user_privileges",
// license
"license.get_trial_status", "license.get_basic_status"
);
private RestClient restClient;
private RestHighLevelClient restHighLevelClient;
@ -783,9 +798,7 @@ public class RestHighLevelClientTests extends ESTestCase {
assertEquals("incorrect number of exceptions for method [" + method + "]", 1, method.getExceptionTypes().length);
//a few methods don't accept a request object as argument
if (apiName.equals("ping") || apiName.equals("info") || apiName.equals("security.get_ssl_certificates")
|| apiName.equals("security.authenticate") || apiName.equals("license.get_trial_status")
|| apiName.equals("license.get_basic_status")) {
if (APIS_WITHOUT_REQUEST_OBJECT.contains(apiName)) {
assertEquals("incorrect number of arguments for method [" + method + "]", 1, method.getParameterTypes().length);
assertThat("the parameter to method [" + method + "] is the wrong type",
method.getParameterTypes()[0], equalTo(RequestOptions.class));
@ -803,7 +816,7 @@ public class RestHighLevelClientTests extends ESTestCase {
methods.containsKey(apiName.substring(0, apiName.length() - 6)));
assertThat("async method [" + method + "] should return void", method.getReturnType(), equalTo(Void.TYPE));
assertEquals("async method [" + method + "] should not throw any exceptions", 0, method.getExceptionTypes().length);
if (apiName.equals("security.authenticate_async") || apiName.equals("security.get_ssl_certificates_async")) {
if (APIS_WITHOUT_REQUEST_OBJECT.contains(apiName.replaceAll("_async$", ""))) {
assertEquals(2, method.getParameterTypes().length);
assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class));
assertThat(method.getParameterTypes()[1], equalTo(ActionListener.class));

View File

@ -53,8 +53,11 @@ import org.elasticsearch.client.security.GetRoleMappingsResponse;
import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.GetRolesResponse;
import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.GetUserPrivilegesResponse;
import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges;
import org.elasticsearch.client.security.user.privileges.UserIndicesPrivileges;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.PutPrivilegesRequest;
@ -697,6 +700,66 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
}
}
public void testGetUserPrivileges() throws Exception {
RestHighLevelClient client = highLevelClient();
{
//tag::get-user-privileges-execute
GetUserPrivilegesResponse response = client.security().getUserPrivileges(RequestOptions.DEFAULT);
//end::get-user-privileges-execute
assertNotNull(response);
//tag::get-user-privileges-response
final Set<String> cluster = response.getClusterPrivileges();
final Set<UserIndicesPrivileges> index = response.getIndicesPrivileges();
final Set<ApplicationResourcePrivileges> application = response.getApplicationPrivileges();
final Set<String> runAs = response.getRunAsPrivilege();
//end::get-user-privileges-response
assertNotNull(cluster);
assertThat(cluster, contains("all"));
assertNotNull(index);
assertThat(index.size(), is(1));
final UserIndicesPrivileges indexPrivilege = index.iterator().next();
assertThat(indexPrivilege.getIndices(), contains("*"));
assertThat(indexPrivilege.getPrivileges(), contains("all"));
assertThat(indexPrivilege.getFieldSecurity().size(), is(0));
assertThat(indexPrivilege.getQueries().size(), is(0));
assertNotNull(application);
assertThat(application.size(), is(1));
assertNotNull(runAs);
assertThat(runAs, contains("*"));
}
{
//tag::get-user-privileges-execute-listener
ActionListener<GetUserPrivilegesResponse> listener = new ActionListener<GetUserPrivilegesResponse>() {
@Override
public void onResponse(GetUserPrivilegesResponse getUserPrivilegesResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
//end::get-user-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::get-user-privileges-execute-async
client.security().getUserPrivilegesAsync(RequestOptions.DEFAULT, listener); // <1>
// end::get-user-privileges-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
public void testClearRealmCache() throws Exception {
RestHighLevelClient client = highLevelClient();
{
@ -1513,5 +1576,5 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
}
}

View File

@ -0,0 +1,131 @@
/*
* 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.GlobalOperationPrivilege;
import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
import org.elasticsearch.client.security.user.privileges.UserIndicesPrivileges;
import org.elasticsearch.common.util.iterable.Iterables;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import java.util.Iterator;
import java.util.List;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.hamcrest.Matchers.nullValue;
public class GetUserPrivilegesResponseTests extends ESTestCase {
public void testParse() throws Exception {
String json = "{" +
"\"cluster\":[\"manage\",\"manage_security\",\"monitor\"]," +
"\"global\":[" +
" {\"application\":{\"manage\":{\"applications\":[\"test-*\"]}}}," +
" {\"application\":{\"manage\":{\"applications\":[\"apps-*\"]}}}" +
"]," +
"\"indices\":[" +
" {\"names\":[\"test-1-*\"],\"privileges\":[\"read\"]}," +
" {\"names\":[\"test-4-*\"],\"privileges\":[\"read\"],\"field_security\":[{\"grant\":[\"*\"],\"except\":[\"private-*\"]}]}," +
" {\"names\":[\"test-6-*\",\"test-7-*\"],\"privileges\":[\"read\"]," +
" \"query\":[\"{\\\"term\\\":{\\\"test\\\":true}}\"]}," +
" {\"names\":[\"test-2-*\"],\"privileges\":[\"read\"]," +
" \"field_security\":[{\"grant\":[\"*\"],\"except\":[\"secret-*\",\"private-*\"]},{\"grant\":[\"apps-*\"]}]," +
" \"query\":[\"{\\\"term\\\":{\\\"test\\\":true}}\",\"{\\\"term\\\":{\\\"apps\\\":true}}\"]}," +
" {\"names\":[\"test-3-*\",\"test-6-*\"],\"privileges\":[\"read\",\"write\"]}," +
" {\"names\":[\"test-3-*\",\"test-4-*\",\"test-5-*\"],\"privileges\":[\"read\"]," +
" \"field_security\":[{\"grant\":[\"test-*\"]}]}," +
" {\"names\":[\"test-1-*\",\"test-9-*\"],\"privileges\":[\"all\"]}" +
"]," +
"\"applications\":[" +
" {\"application\":\"app-dne\",\"privileges\":[\"all\"],\"resources\":[\"*\"]}," +
" {\"application\":\"test-app\",\"privileges\":[\"read\"],\"resources\":[\"object/1\",\"object/2\"]}," +
" {\"application\":\"test-app\",\"privileges\":[\"user\",\"dne\"],\"resources\":[\"*\"]}" +
"]," +
"\"run_as\":[\"app-*\",\"test-*\"]}";
final XContentParser parser = createParser(XContentType.JSON.xContent(), json);
final GetUserPrivilegesResponse response = GetUserPrivilegesResponse.fromXContent(parser);
assertThat(response.getClusterPrivileges(), contains("manage", "manage_security", "monitor"));
assertThat(response.getGlobalPrivileges().size(), equalTo(2));
assertThat(Iterables.get(response.getGlobalPrivileges(), 0).getPrivileges().size(), equalTo(1));
assertManageApplicationsPrivilege(Iterables.get(response.getGlobalPrivileges(), 0).getPrivileges().iterator().next(), "test-*");
assertThat(Iterables.get(response.getGlobalPrivileges(), 1).getPrivileges().size(), equalTo(1));
assertManageApplicationsPrivilege(Iterables.get(response.getGlobalPrivileges(), 1).getPrivileges().iterator().next(), "apps-*");
assertThat(response.getIndicesPrivileges().size(), equalTo(7));
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getIndices(), contains("test-1-*"));
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getPrivileges(), contains("read"));
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getFieldSecurity(), emptyIterable());
assertThat(Iterables.get(response.getIndicesPrivileges(), 0).getQueries(), emptyIterable());
final UserIndicesPrivileges test4Privilege = Iterables.get(response.getIndicesPrivileges(), 1);
assertThat(test4Privilege.getIndices(), contains("test-4-*"));
assertThat(test4Privilege.getPrivileges(), contains("read"));
assertThat(test4Privilege.getFieldSecurity(), iterableWithSize(1));
final IndicesPrivileges.FieldSecurity test4FLS = test4Privilege.getFieldSecurity().iterator().next();
assertThat(test4FLS.getGrantedFields(), contains("*"));
assertThat(test4FLS.getDeniedFields(), contains("private-*"));
assertThat(test4Privilege.getQueries(), emptyIterable());
final UserIndicesPrivileges test2Privilege = Iterables.get(response.getIndicesPrivileges(), 3);
assertThat(test2Privilege.getIndices(), contains("test-2-*"));
assertThat(test2Privilege.getPrivileges(), contains("read"));
assertThat(test2Privilege.getFieldSecurity(), iterableWithSize(2));
final Iterator<IndicesPrivileges.FieldSecurity> test2FLSIter = test2Privilege.getFieldSecurity().iterator();
final IndicesPrivileges.FieldSecurity test2FLS1 = test2FLSIter.next();
final IndicesPrivileges.FieldSecurity test2FLS2 = test2FLSIter.next();
assertThat(test2FLS1.getGrantedFields(), contains("*"));
assertThat(test2FLS1.getDeniedFields(), containsInAnyOrder("secret-*", "private-*"));
assertThat(test2FLS2.getGrantedFields(), contains("apps-*"));
assertThat(test2FLS2.getDeniedFields(), nullValue());
assertThat(test2Privilege.getQueries(), iterableWithSize(2));
final Iterator<String> test2QueryIter = test2Privilege.getQueries().iterator();
assertThat(test2QueryIter.next(), equalTo("{\"term\":{\"test\":true}}"));
assertThat(test2QueryIter.next(), equalTo("{\"term\":{\"apps\":true}}"));
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getIndices(), contains("test-1-*", "test-9-*"));
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getPrivileges(), contains("all"));
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getFieldSecurity(), emptyIterable());
assertThat(Iterables.get(response.getIndicesPrivileges(), 6).getQueries(), emptyIterable());
assertThat(response.getApplicationPrivileges().size(), equalTo(3));
assertThat(Iterables.get(response.getApplicationPrivileges(), 1).getApplication(), equalTo("test-app"));
assertThat(Iterables.get(response.getApplicationPrivileges(), 1).getPrivileges(), contains("read"));
assertThat(Iterables.get(response.getApplicationPrivileges(), 1).getResources(), containsInAnyOrder("object/1", "object/2"));
assertThat(response.getRunAsPrivilege(), contains("app-*", "test-*"));
}
private void assertManageApplicationsPrivilege(GlobalOperationPrivilege privilege, String... applications) {
assertThat(privilege.getCategory(), equalTo("application"));
assertThat(privilege.getOperation(), equalTo("manage"));
assertThat(privilege.getRaw().keySet(), contains("applications"));
assertThat(privilege.getRaw().get("applications"), instanceOf(List.class));
assertThat((List<?>) privilege.getRaw().get("applications"), contains((Object[]) applications));
}
}

View File

@ -19,13 +19,18 @@
package org.elasticsearch.client.security.user.privileges;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
public class IndicesPrivilegesTests extends AbstractXContentTestCase<IndicesPrivileges> {
public static IndicesPrivileges createNewRandom(String query) {
@ -43,6 +48,35 @@ public class IndicesPrivilegesTests extends AbstractXContentTestCase<IndicesPriv
return indicesPrivilegesBuilder.build();
}
public void testToXContentWithNullFieldSecurity() {
final IndicesPrivileges privileges = IndicesPrivileges.builder().indices("abc").privileges("all").build();
final String json = Strings.toString(privileges);
assertThat(json, not(containsString("field_security")));
}
public void testToXContentWithEmptyFieldSecurity() {
final IndicesPrivileges privileges = IndicesPrivileges.builder()
.indices("abc")
.privileges("all")
.grantedFields(Collections.emptyList())
.deniedFields(Collections.emptyList())
.build();
final String json = Strings.toString(privileges);
assertThat(json, containsString("field_security"));
assertThat(json, containsString("\"field_security\":{\"grant\":[],\"except\":[]}"));
}
public void testToXContentWithDeniedFieldsOnly() {
final IndicesPrivileges privileges = IndicesPrivileges.builder()
.indices("abc")
.privileges("all")
.deniedFields("secret.*")
.build();
final String json = Strings.toString(privileges);
assertThat(json, containsString("field_security"));
assertThat(json, containsString("\"field_security\":{\"grant\":[\"*\"],\"except\":[\"secret.*\"]}"));
}
@Override
protected IndicesPrivileges createTestInstance() {
return createNewRandom(

View File

@ -0,0 +1,55 @@
////
This file is included by high level rest client API documentation pages
where the client method does not use a request object.
For methods with requests, see execution.asciidoc
////
[id="{upid}-{api}-sync"]
==== Synchronous Execution
When executing the +{api}+ API in the following manner, the client waits
for the +{response}+ to be returned before continuing with code execution:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-execute]
--------------------------------------------------
Synchronous calls may throw an `IOException` in case of either failing to
parse the REST response in the high-level REST client, the request times out
or similar cases where there is no response coming back from the server.
In cases where the server returns a `4xx` or `5xx` error code, the high-level
client tries to parse the response body error details instead and then throws
a generic `ElasticsearchException` and adds the original `ResponseException` as a
suppressed exception to it.
[id="{upid}-{api}-async"]
==== Asynchronous Execution
The +{api}+ API can also be called in an asynchronous fashion so that
the client can return directly. Users need to specify how the response or
potential failures will be handled by passing a listener to the
asynchronous {api} method:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-execute-async]
--------------------------------------------------
<1> The `RequestOptions` and `ActionListener` to use when the execution
completes
The asynchronous method does not block and returns immediately. Once it is
completed the `ActionListener` is called back using the `onResponse` method
if the execution successfully completed or using the `onFailure` method if
it failed. Failure scenarios and expected exceptions are the same as in the
synchronous execution case.
A typical listener for +{api}+ looks like:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-execute-listener]
--------------------------------------------------
<1> Called when the execution is successfully completed.
<2> Called when the +{api}+ call fails.

View File

@ -0,0 +1,46 @@
--
:api: get-user-privileges
:request: GetUserPrivilegesRequest
:response: GetUserPrivilegesResponse
--
[id="{upid}-{api}"]
=== Get User Privileges API
include::../execution-no-req.asciidoc[]
[id="{upid}-{api}-response"]
==== Get User Privileges Response
The returned +{response}+ contains the following properties
`clusterPrivileges`::
A `Set` of all _cluster_ privileges that are held by the user.
This will be the union of all the _cluster_ privileges from the user's roles.
`globalPrivileges`::
A `Set` of all _global_ privileges that are held by the user.
This will be the union of all the _global_ privileges from the user's roles.
Because this a union of multiple roles, it may contain multiple privileges for
the same `category` and `operation` (which is why is is represented as a `Set`
rather than a single object).
`indicesPrivileges`::
A `Set` of all _index_ privileges that are held by the user.
This will be the union of all the _index_ privileges from the user's roles.
Because this a union of multiple roles, it may contain multiple privileges for
the same `index`, and those privileges may have independent field level security
access grants and/or multiple document level security queries.
`applicationPrivileges`::
A `Set` of all _application_ privileges that are held by the user.
This will be the union of all the _application_ privileges from the user's roles.
`runAsPrivilege`::
A `Set` representation of the _run-as_ privilege that is held by the user.
This will be the union of the _run-as_ privilege from each of the user's roles.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------

View File

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