Add get-user-privileges API (#33928)

This API is intended as a companion to the _has_privileges API.
It returns the list of privileges that are held by the current user.

This information is difficult to reason about, and consumers should
avoid making direct security decisions based solely on this data.
For example, each of the following index privileges (as well as many
more) would grant a user access to index a new document into the
"metrics-2018-08-30" index, but clients should not try and deduce
that information from this API.
- "all" on "*"
- "all" on "metrics-*"
- "write" on "metrics-2018-*"
- "write" on "metrics-2018-08-30"

Rather, if a client wished to know if a user had "index" access to
_any_ index, it would be possible to use this API to determine whether
the user has any index privileges, and on which index patterns, and
then feed those index patterns into _has_privileges in order to
determine whether the "index" privilege had been granted.

The result JSON is modelled on the Role API, with a few small changes
to reflect how privileges are modelled when multiple roles are merged
together (multiple DLS queries, multiple FLS grants, multiple global
conditions, etc).
This commit is contained in:
Tim Vernum 2018-10-18 14:09:04 +11:00 committed by GitHub
parent d445785f1a
commit 9200e15b74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1486 additions and 35 deletions

View File

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.Action;
/**
* Action that lists the set of privileges held by a user.
*/
public final class GetUserPrivilegesAction extends Action<GetUserPrivilegesResponse> {
public static final GetUserPrivilegesAction INSTANCE = new GetUserPrivilegesAction();
public static final String NAME = "cluster:admin/xpack/security/user/list_privileges";
private GetUserPrivilegesAction() {
super(NAME);
}
@Override
public GetUserPrivilegesResponse newResponse() {
return new GetUserPrivilegesResponse();
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
/**
* A request for checking a user's privileges
*/
public final class GetUserPrivilegesRequest extends ActionRequest implements UserRequest {
private String username;
/**
* Package level access for {@link GetUserPrivilegesRequestBuilder}.
*/
GetUserPrivilegesRequest() {
}
public GetUserPrivilegesRequest(StreamInput in) throws IOException {
super(in);
this.username = in.readString();
}
@Override
public ActionRequestValidationException validate() {
return null;
}
/**
* @return the username that this request applies to.
*/
public String username() {
return username;
}
/**
* Set the username that the request applies to. Must not be {@code null}
*/
public void username(String username) {
this.username = username;
}
@Override
public String[] usernames() {
return new String[] { username };
}
/**
* Always throws {@link UnsupportedOperationException} as this object should be deserialized using
* the {@link #GetUserPrivilegesRequest(StreamInput)} constructor instead.
*/
@Override
@Deprecated
public void readFrom(StreamInput in) throws IOException {
throw new UnsupportedOperationException("Use " + getClass() + " as Writeable not Streamable");
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(username);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
/**
* Request builder for checking a user's privileges
*/
public class GetUserPrivilegesRequestBuilder
extends ActionRequestBuilder<GetUserPrivilegesRequest, GetUserPrivilegesResponse> {
public GetUserPrivilegesRequestBuilder(ElasticsearchClient client) {
super(client, GetUserPrivilegesAction.INSTANCE, new GetUserPrivilegesRequest());
}
/**
* Set the username of the user whose privileges should be retrieved. Must not be {@code null}
*/
public GetUserPrivilegesRequestBuilder username(String username) {
request.username(username);
return this;
}
}

View File

@ -0,0 +1,242 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* Response for a {@link GetUserPrivilegesRequest}
*/
public final class GetUserPrivilegesResponse extends ActionResponse {
private Set<String> cluster;
private Set<ConditionalClusterPrivilege> conditionalCluster;
private Set<Indices> index;
private Set<RoleDescriptor.ApplicationResourcePrivileges> application;
private Set<String> runAs;
public GetUserPrivilegesResponse() {
this(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
}
public GetUserPrivilegesResponse(Set<String> cluster, Set<ConditionalClusterPrivilege> conditionalCluster,
Set<Indices> index,
Set<RoleDescriptor.ApplicationResourcePrivileges> application,
Set<String> runAs) {
this.cluster = Collections.unmodifiableSet(cluster);
this.conditionalCluster = Collections.unmodifiableSet(conditionalCluster);
this.index = Collections.unmodifiableSet(index);
this.application = Collections.unmodifiableSet(application);
this.runAs = Collections.unmodifiableSet(runAs);
}
public Set<String> getClusterPrivileges() {
return cluster;
}
public Set<ConditionalClusterPrivilege> getConditionalClusterPrivileges() {
return conditionalCluster;
}
public Set<Indices> getIndexPrivileges() {
return index;
}
public Set<RoleDescriptor.ApplicationResourcePrivileges> getApplicationPrivileges() {
return application;
}
public Set<String> getRunAs() {
return runAs;
}
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
cluster = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
conditionalCluster = Collections.unmodifiableSet(in.readSet(ConditionalClusterPrivileges.READER));
index = Collections.unmodifiableSet(in.readSet(Indices::new));
application = Collections.unmodifiableSet(in.readSet(RoleDescriptor.ApplicationResourcePrivileges::createFrom));
runAs = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeCollection(cluster, StreamOutput::writeString);
out.writeCollection(conditionalCluster, ConditionalClusterPrivileges.WRITER);
out.writeCollection(index, (o, p) -> p.writeTo(o));
out.writeCollection(application, (o, p) -> p.writeTo(o));
out.writeCollection(runAs, StreamOutput::writeString);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (other == null || getClass() != other.getClass()) {
return false;
}
final GetUserPrivilegesResponse that = (GetUserPrivilegesResponse) other;
return Objects.equals(cluster, that.cluster) &&
Objects.equals(conditionalCluster, that.conditionalCluster) &&
Objects.equals(index, that.index) &&
Objects.equals(application, that.application) &&
Objects.equals(runAs, that.runAs);
}
@Override
public int hashCode() {
return Objects.hash(cluster, conditionalCluster, index, application, runAs);
}
/**
* This is modelled on {@link RoleDescriptor.IndicesPrivileges}, with support for multiple DLS and FLS field sets.
*/
public static class Indices implements ToXContentObject, Writeable {
private final Set<String> indices;
private final Set<String> privileges;
private final Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> fieldSecurity;
private final Set<BytesReference> queries;
public Indices(Collection<String> indices, Collection<String> privileges,
Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> fieldSecurity, Set<BytesReference> queries) {
// The use of TreeSet is to provide a consistent order that can be relied upon in tests
this.indices = Collections.unmodifiableSet(new TreeSet<>(Objects.requireNonNull(indices)));
this.privileges = Collections.unmodifiableSet(new TreeSet<>(Objects.requireNonNull(privileges)));
this.fieldSecurity = Collections.unmodifiableSet(Objects.requireNonNull(fieldSecurity));
this.queries = Collections.unmodifiableSet(Objects.requireNonNull(queries));
}
public Indices(StreamInput in) throws IOException {
indices = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
privileges = Collections.unmodifiableSet(in.readSet(StreamInput::readString));
fieldSecurity = Collections.unmodifiableSet(in.readSet(input -> {
final String[] grant = input.readOptionalStringArray();
final String[] exclude = input.readOptionalStringArray();
return new FieldPermissionsDefinition.FieldGrantExcludeGroup(grant, exclude);
}));
queries = Collections.unmodifiableSet(in.readSet(StreamInput::readBytesReference));
}
public Set<String> getIndices() {
return indices;
}
public Set<String> getPrivileges() {
return privileges;
}
public Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> getFieldSecurity() {
return fieldSecurity;
}
public Set<BytesReference> getQueries() {
return queries;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(getClass().getSimpleName())
.append("[")
.append("indices=[").append(Strings.collectionToCommaDelimitedString(indices))
.append("], privileges=[").append(Strings.collectionToCommaDelimitedString(privileges))
.append("]");
if (fieldSecurity.isEmpty() == false) {
sb.append(", fls=[").append(Strings.collectionToCommaDelimitedString(fieldSecurity)).append("]");
}
if (queries.isEmpty() == false) {
sb.append(", dls=[")
.append(queries.stream().map(BytesReference::utf8ToString).collect(Collectors.joining(",")))
.append("]");
}
sb.append("]");
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Indices that = (Indices) o;
return this.indices.equals(that.indices)
&& this.privileges.equals(that.privileges)
&& this.fieldSecurity.equals(that.fieldSecurity)
&& this.queries.equals(that.queries);
}
@Override
public int hashCode() {
return Objects.hash(indices, privileges, fieldSecurity, queries);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(RoleDescriptor.Fields.NAMES.getPreferredName(), indices);
builder.field(RoleDescriptor.Fields.PRIVILEGES.getPreferredName(), privileges);
if (fieldSecurity.stream().anyMatch(g -> nonEmpty(g.getGrantedFields()) || nonEmpty(g.getExcludedFields()))) {
builder.startArray(RoleDescriptor.Fields.FIELD_PERMISSIONS.getPreferredName());
for (FieldPermissionsDefinition.FieldGrantExcludeGroup group : this.fieldSecurity) {
builder.startObject();
if (nonEmpty(group.getGrantedFields())) {
builder.array(RoleDescriptor.Fields.GRANT_FIELDS.getPreferredName(), group.getGrantedFields());
}
if (nonEmpty(group.getExcludedFields())) {
builder.array(RoleDescriptor.Fields.EXCEPT_FIELDS.getPreferredName(), group.getExcludedFields());
}
builder.endObject();
}
builder.endArray();
}
if (queries.isEmpty() == false) {
builder.startArray(RoleDescriptor.Fields.QUERY.getPreferredName());
for (BytesReference q : queries) {
builder.value(q.utf8ToString());
}
builder.endArray();
}
return builder.endObject();
}
private boolean nonEmpty(String[] grantedFields) {
return grantedFields != null && grantedFields.length != 0;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeCollection(indices, StreamOutput::writeString);
out.writeCollection(privileges, StreamOutput::writeString);
out.writeCollection(fieldSecurity, (output, fields) -> {
output.writeOptionalStringArray(fields.getGrantedFields());
output.writeOptionalStringArray(fields.getExcludedFields());
});
out.writeCollection(queries, StreamOutput::writeBytesReference);
}
}
}

View File

@ -763,6 +763,10 @@ public class RoleDescriptor implements ToXContentObject {
return this;
}
public Builder privileges(Collection<String> privileges) {
return privileges(privileges.toArray(new String[privileges.size()]));
}
public Builder grantedFields(String... grantedFields) {
indicesPrivileges.grantedFields = grantedFields;
return this;
@ -919,7 +923,7 @@ public class RoleDescriptor implements ToXContentObject {
return this;
}
public Builder resources(List<String> resources) {
public Builder resources(Collection<String> resources) {
return resources(resources.toArray(new String[resources.size()]));
}

View File

@ -5,11 +5,12 @@
*/
package org.elasticsearch.xpack.core.security.authz.permission;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.support.Automatons;
@ -21,6 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A permission that is based on privileges for application (non elasticsearch) capabilities
@ -38,14 +40,16 @@ public final class ApplicationPermission {
* applied. The resources are treated as a wildcard {@link Automatons#pattern}.
*/
ApplicationPermission(List<Tuple<ApplicationPrivilege, Set<String>>> privilegesAndResources) {
this.logger = Loggers.getLogger(getClass());
this.logger = LogManager.getLogger(getClass());
Map<ApplicationPrivilege, PermissionEntry> permissionsByPrivilege = new HashMap<>();
privilegesAndResources.forEach(tup -> permissionsByPrivilege.compute(tup.v1(), (k, existing) -> {
final Automaton patterns = Automatons.patterns(tup.v2());
privilegesAndResources.forEach(tup -> permissionsByPrivilege.compute(tup.v1(), (appPriv, existing) -> {
final Set<String> resourceNames = tup.v2();
final Automaton patterns = Automatons.patterns(resourceNames);
if (existing == null) {
return new PermissionEntry(k, patterns);
return new PermissionEntry(appPriv, resourceNames, patterns);
} else {
return new PermissionEntry(k, Automatons.unionAndMinimize(Arrays.asList(existing.resources, patterns)));
return new PermissionEntry(appPriv, Sets.union(existing.resourceNames, resourceNames),
Automatons.unionAndMinimize(Arrays.asList(existing.resourceAutomaton, patterns)));
}
}));
this.permissions = Collections.unmodifiableList(new ArrayList<>(permissionsByPrivilege.values()));
@ -84,27 +88,73 @@ public final class ApplicationPermission {
return getClass().getSimpleName() + "{privileges=" + permissions + "}";
}
public Set<String> getApplicationNames() {
return permissions.stream()
.map(e -> e.privilege.getApplication())
.collect(Collectors.toSet());
}
public Set<ApplicationPrivilege> getPrivileges(String application) {
return permissions.stream()
.filter(e -> application.equals(e.privilege.getApplication()))
.map(e -> e.privilege)
.collect(Collectors.toSet());
}
/**
* Returns a set of resource patterns that are permitted for the provided privilege.
* The returned set may include patterns that overlap (e.g. "object/*" and "object/1") and may
* also include patterns that are defined again a more permissive privilege.
* e.g. If a permission grants
* <ul>
* <li>"my-app", "read", [ "user/*" ]</li>
* <li>"my-app", "all", [ "user/kimchy", "config/*" ]</li>
* </ul>
* Then <code>getResourcePatterns( myAppRead )</code> would return <code>"user/*", "user/kimchy", "config/*"</code>.
*/
public Set<String> getResourcePatterns(ApplicationPrivilege privilege) {
return permissions.stream()
.filter(e -> e.matchesPrivilege(privilege))
.map(e -> e.resourceNames)
.flatMap(Set::stream)
.collect(Collectors.toSet());
}
private static class PermissionEntry {
private final ApplicationPrivilege privilege;
private final Predicate<String> application;
private final Automaton resources;
private final Set<String> resourceNames;
private final Automaton resourceAutomaton;
private PermissionEntry(ApplicationPrivilege privilege, Automaton resources) {
private PermissionEntry(ApplicationPrivilege privilege, Set<String> resourceNames, Automaton resourceAutomaton) {
this.privilege = privilege;
this.application = Automatons.predicate(privilege.getApplication());
this.resources = resources;
this.resourceNames = resourceNames;
this.resourceAutomaton = resourceAutomaton;
}
private boolean grants(ApplicationPrivilege other, Automaton resource) {
return this.application.test(other.getApplication())
&& Operations.isEmpty(privilege.getAutomaton()) == false
&& Operations.subsetOf(other.getAutomaton(), privilege.getAutomaton())
&& Operations.subsetOf(resource, this.resources);
return matchesPrivilege(other) && Operations.subsetOf(resource, this.resourceAutomaton);
}
private boolean matchesPrivilege(ApplicationPrivilege other) {
if (this.privilege.equals(other)) {
return true;
}
if (this.application.test(other.getApplication()) == false) {
return false;
}
if (Operations.isTotal(privilege.getAutomaton())) {
return true;
}
return Operations.isEmpty(privilege.getAutomaton()) == false
&& Operations.isEmpty(other.getAutomaton()) == false
&& Operations.subsetOf(other.getAutomaton(), privilege.getAutomaton());
}
@Override
public String toString() {
return privilege.toString();
return privilege.toString() + ":" + resourceNames;
}
}
}

View File

@ -5,11 +5,14 @@
*/
package org.elasticsearch.xpack.core.security.authz.permission;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@ -30,6 +33,8 @@ public abstract class ClusterPermission {
public abstract boolean check(String action, TransportRequest request);
public abstract List<Tuple<ClusterPrivilege, ConditionalClusterPrivilege>> privileges();
/**
* A permission that is based solely on cluster privileges and does not consider request state
*/
@ -48,28 +53,32 @@ public abstract class ClusterPermission {
public boolean check(String action, TransportRequest request) {
return predicate.test(action);
}
@Override
public List<Tuple<ClusterPrivilege, ConditionalClusterPrivilege>> privileges() {
return Collections.singletonList(new Tuple<>(super.privilege, null));
}
}
/**
* A permission that makes use of both cluster privileges and request inspection
*/
public static class ConditionalClusterPermission extends ClusterPermission {
private final Predicate<String> actionPredicate;
private final Predicate<TransportRequest> requestPredicate;
private final ConditionalClusterPrivilege conditionalPrivilege;
public ConditionalClusterPermission(ConditionalClusterPrivilege conditionalPrivilege) {
this(conditionalPrivilege.getPrivilege(), conditionalPrivilege.getRequestPredicate());
}
public ConditionalClusterPermission(ClusterPrivilege privilege, Predicate<TransportRequest> requestPredicate) {
super(privilege);
this.actionPredicate = privilege.predicate();
this.requestPredicate = requestPredicate;
super(conditionalPrivilege.getPrivilege());
this.conditionalPrivilege = conditionalPrivilege;
}
@Override
public boolean check(String action, TransportRequest request) {
return actionPredicate.test(action) && requestPredicate.test(request);
return super.privilege.predicate().test(action) && conditionalPrivilege.getRequestPredicate().test(request);
}
@Override
public List<Tuple<ClusterPrivilege, ConditionalClusterPrivilege>> privileges() {
return Collections.singletonList(new Tuple<>(super.privilege, conditionalPrivilege));
}
}
@ -93,6 +102,11 @@ public abstract class ClusterPermission {
return ClusterPrivilege.get(names);
}
@Override
public List<Tuple<ClusterPrivilege, ConditionalClusterPrivilege>> privileges() {
return children.stream().map(ClusterPermission::privileges).flatMap(List::stream).collect(Collectors.toList());
}
@Override
public boolean check(String action, TransportRequest request) {
return children.stream().anyMatch(p -> p.check(action, request));

View File

@ -161,7 +161,7 @@ public final class FieldPermissions implements Accountable {
return permittedFieldsAutomatonIsTotal || permittedFieldsAutomaton.run(fieldName);
}
FieldPermissionsDefinition getFieldPermissionsDefinition() {
public FieldPermissionsDefinition getFieldPermissionsDefinition() {
return fieldPermissionsDefinition;
}

View File

@ -5,6 +5,8 @@
*/
package org.elasticsearch.xpack.core.security.authz.permission;
import org.elasticsearch.common.Strings;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
@ -81,5 +83,13 @@ public final class FieldPermissionsDefinition {
result = 31 * result + Arrays.hashCode(excludedFields);
return result;
}
@Override
public String toString() {
return getClass().getSimpleName()
+ "[grant=" + Strings.arrayToCommaDelimitedString(grantedFields)
+ "; exclude=" + Strings.arrayToCommaDelimitedString(excludedFields)
+ "]";
}
}
}

View File

@ -201,7 +201,7 @@ public final class Role {
static Tuple<ApplicationPrivilege, Set<String>> convertApplicationPrivilege(String role, int index,
RoleDescriptor.ApplicationResourcePrivileges arp) {
return new Tuple<>(new ApplicationPrivilege(arp.getApplication(),
"role." + role.replaceAll("[^a-zA-Z0-9]", "") + "." + index,
Sets.newHashSet(arp.getPrivileges()),
arp.getPrivileges()
), Sets.newHashSet(arp.getResources()));
}

View File

@ -17,12 +17,18 @@ public final class RunAsPermission {
public static final RunAsPermission NONE = new RunAsPermission(Privilege.NONE);
private final Privilege privilege;
private final Predicate<String> predicate;
RunAsPermission(Privilege privilege) {
this.privilege = privilege;
this.predicate = privilege.predicate();
}
public Privilege getPrivilege() {
return privilege;
}
/**
* Checks if this permission grants run as to the specified user
*/

View File

@ -10,6 +10,7 @@ import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParseException;
@ -37,6 +38,11 @@ public final class ConditionalClusterPrivileges {
public static final ConditionalClusterPrivilege[] EMPTY_ARRAY = new ConditionalClusterPrivilege[0];
public static final Writeable.Reader<ConditionalClusterPrivilege> READER =
in1 -> in1.readNamedWriteable(ConditionalClusterPrivilege.class);
public static final Writeable.Writer<ConditionalClusterPrivilege> WRITER =
(out1, value) -> out1.writeNamedWriteable(value);
private ConditionalClusterPrivileges() {
}
@ -44,15 +50,14 @@ public final class ConditionalClusterPrivileges {
* Utility method to read an array of {@link ConditionalClusterPrivilege} objects from a {@link StreamInput}
*/
public static ConditionalClusterPrivilege[] readArray(StreamInput in) throws IOException {
return in.readArray(in1 ->
in1.readNamedWriteable(ConditionalClusterPrivilege.class), ConditionalClusterPrivilege[]::new);
return in.readArray(READER, ConditionalClusterPrivilege[]::new);
}
/**
* Utility method to write an array of {@link ConditionalClusterPrivilege} objects to a {@link StreamOutput}
*/
public static void writeArray(StreamOutput out, ConditionalClusterPrivilege[] privileges) throws IOException {
out.writeArray((out1, value) -> out1.writeNamedWriteable(value), privileges);
out.writeArray(WRITER, privileges);
}
/**

View File

@ -74,6 +74,10 @@ import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder;
@ -173,6 +177,14 @@ public class SecurityClient {
client.execute(HasPrivilegesAction.INSTANCE, request, listener);
}
public GetUserPrivilegesRequestBuilder prepareGetUserPrivileges(String username) {
return new GetUserPrivilegesRequestBuilder(client).username(username);
}
public void listUserPrivileges(GetUserPrivilegesRequest request, ActionListener<GetUserPrivilegesResponse> listener) {
client.execute(GetUserPrivilegesAction.INSTANCE, request, listener);
}
/**
* User Management
*/

View File

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import java.io.IOException;
import static org.hamcrest.Matchers.equalTo;
public class GetUserPrivilegesRequestTests extends ESTestCase {
public void testSerialization() throws IOException {
final String user = randomAlphaOfLengthBetween(3, 12);
final GetUserPrivilegesRequest original = new GetUserPrivilegesRequest();
original.username(user);
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
StreamInput in = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(out.bytes())), registry);
final GetUserPrivilegesRequest copy = new GetUserPrivilegesRequest(in);
assertThat(copy.username(), equalTo(original.username()));
assertThat(copy.usernames(), equalTo(original.usernames()));
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.user;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.EqualsHashCodeTestUtils;
import org.elasticsearch.xpack.core.XPackClientPlugin;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition.FieldGrantExcludeGroup;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static java.util.Collections.emptySet;
import static org.hamcrest.Matchers.equalTo;
public class GetUserPrivilegesResponseTests extends ESTestCase {
public void testSerialization() throws IOException {
final GetUserPrivilegesResponse original = randomResponse();
final BytesStreamOutput out = new BytesStreamOutput();
original.writeTo(out);
final GetUserPrivilegesResponse copy = new GetUserPrivilegesResponse();
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
StreamInput in = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(out.bytes())), registry);
copy.readFrom(in);
assertThat(copy.getClusterPrivileges(), equalTo(original.getClusterPrivileges()));
assertThat(copy.getConditionalClusterPrivileges(), equalTo(original.getConditionalClusterPrivileges()));
assertThat(sorted(copy.getIndexPrivileges()), equalTo(sorted(original.getIndexPrivileges())));
assertThat(copy.getApplicationPrivileges(), equalTo(original.getApplicationPrivileges()));
assertThat(copy.getRunAs(), equalTo(original.getRunAs()));
}
public void testEqualsAndHashCode() throws IOException {
final GetUserPrivilegesResponse response = randomResponse();
final EqualsHashCodeTestUtils.CopyFunction<GetUserPrivilegesResponse> copy = original -> new GetUserPrivilegesResponse(
original.getClusterPrivileges(),
original.getConditionalClusterPrivileges(),
original.getIndexPrivileges(),
original.getApplicationPrivileges(),
original.getRunAs()
);
final EqualsHashCodeTestUtils.MutateFunction<GetUserPrivilegesResponse> mutate =
new EqualsHashCodeTestUtils.MutateFunction<GetUserPrivilegesResponse>() {
@Override
public GetUserPrivilegesResponse mutate(GetUserPrivilegesResponse original) {
final int random = randomIntBetween(1, 0b11111);
final Set<String> cluster = maybeMutate(random, 1, original.getClusterPrivileges(), () -> randomAlphaOfLength(5));
final Set<ConditionalClusterPrivilege> conditionalCluster = maybeMutate(random, 2,
original.getConditionalClusterPrivileges(), () -> new ManageApplicationPrivileges(randomStringSet(3)));
final Set<GetUserPrivilegesResponse.Indices> index = maybeMutate(random, 3, original.getIndexPrivileges(),
() -> new GetUserPrivilegesResponse.Indices(randomStringSet(1), randomStringSet(1), emptySet(), emptySet()));
final Set<ApplicationResourcePrivileges> application = maybeMutate(random, 4, original.getApplicationPrivileges(),
() -> ApplicationResourcePrivileges.builder().resources(generateRandomStringArray(3, 3, false, false))
.application(randomAlphaOfLength(5)).privileges(generateRandomStringArray(3, 5, false, false)).build());
final Set<String> runAs = maybeMutate(random, 5, original.getRunAs(), () -> randomAlphaOfLength(8));
return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs);
}
private <T> Set<T> maybeMutate(int random, int index, Set<T> original, Supplier<T> supplier) {
if ((random & (1 << index)) == 0) {
return original;
}
if (original.isEmpty()) {
return Collections.singleton(supplier.get());
} else {
return emptySet();
}
}
};
EqualsHashCodeTestUtils.checkEqualsAndHashCode(response, copy, mutate);
}
private GetUserPrivilegesResponse randomResponse() {
final Set<String> cluster = randomStringSet(5);
final Set<ConditionalClusterPrivilege> conditionalCluster = Sets.newHashSet(randomArray(3, ConditionalClusterPrivilege[]::new,
() -> new ManageApplicationPrivileges(
randomStringSet(3)
)));
final Set<GetUserPrivilegesResponse.Indices> index = Sets.newHashSet(randomArray(5, GetUserPrivilegesResponse.Indices[]::new,
() -> new GetUserPrivilegesResponse.Indices(randomStringSet(6), randomStringSet(8),
Sets.newHashSet(randomArray(3, FieldGrantExcludeGroup[]::new, () -> new FieldGrantExcludeGroup(
generateRandomStringArray(3, 5, false, false), generateRandomStringArray(3, 5, false, false)))),
randomStringSet(3).stream().map(BytesArray::new).collect(Collectors.toSet())
))
);
final Set<ApplicationResourcePrivileges> application = Sets.newHashSet(randomArray(5, ApplicationResourcePrivileges[]::new,
() -> ApplicationResourcePrivileges.builder().resources(generateRandomStringArray(3, 3, false, false))
.application(randomAlphaOfLength(5)).privileges(generateRandomStringArray(3, 5, false, false)).build()
));
final Set<String> runAs = randomStringSet(3);
return new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs);
}
private List<GetUserPrivilegesResponse.Indices> sorted(Collection<GetUserPrivilegesResponse.Indices> indices) {
final ArrayList<GetUserPrivilegesResponse.Indices> list = CollectionUtils.iterableAsArrayList(indices);
Collections.sort(list, (a, b) -> {
int cmp = compareCollection(a.getIndices(), b.getIndices(), String::compareTo);
if (cmp != 0) {
return cmp;
}
cmp = compareCollection(a.getPrivileges(), b.getPrivileges(), String::compareTo);
if (cmp != 0) {
return cmp;
}
cmp = compareCollection(a.getQueries(), b.getQueries(), BytesReference::compareTo);
if (cmp != 0) {
return cmp;
}
cmp = compareCollection(a.getFieldSecurity(), b.getFieldSecurity(), (f1, f2) -> {
int c = compareCollection(Arrays.asList(f1.getGrantedFields()), Arrays.asList(f2.getGrantedFields()), String::compareTo);
if (c == 0) {
c = compareCollection(Arrays.asList(f1.getExcludedFields()), Arrays.asList(f2.getExcludedFields()), String::compareTo);
}
return c;
});
return cmp;
});
return list;
}
private <T> int compareCollection(Collection<T> a, Collection<T> b, Comparator<T> comparator) {
int cmp = Integer.compare(a.size(), b.size());
if (cmp != 0) {
return cmp;
}
Iterator<T> i1 = a.iterator();
Iterator<T> i2 = b.iterator();
while (i1.hasNext()) {
cmp = comparator.compare(i1.next(), i2.next());
if (cmp != 0) {
return cmp;
}
}
return cmp;
}
private HashSet<String> randomStringSet(int maxSize) {
return Sets.newHashSet(generateRandomStringArray(maxSize, randomIntBetween(3, 6), false, false));
}
}

View File

@ -15,8 +15,12 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.singletonList;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
public class ApplicationPermissionTests extends ESTestCase {
@ -101,7 +105,7 @@ public class ApplicationPermissionTests extends ESTestCase {
}
public void testMergedPermissionChecking() {
final ApplicationPrivilege app1ReadWrite = ApplicationPrivilege.get("app1", Sets.union(app1Read.name(), app1Write.name()), store);
final ApplicationPrivilege app1ReadWrite = compositePrivilege("app1", app1Read, app1Write);
final ApplicationPermission hasPermission = buildPermission(app1ReadWrite, "allow/*");
assertThat(hasPermission.grants(app1Read, "allow/1"), equalTo(true));
@ -114,10 +118,35 @@ public class ApplicationPermissionTests extends ESTestCase {
assertThat(hasPermission.grants(app2Read, "allow/1"), equalTo(false));
}
public void testInspectPermissionContents() {
final ApplicationPrivilege app1ReadWrite = compositePrivilege("app1", app1Read, app1Write);
ApplicationPermission perm = new ApplicationPermission(Arrays.asList(
new Tuple<>(app1Read, Sets.newHashSet("obj/1", "obj/2")),
new Tuple<>(app1Write, Sets.newHashSet("obj/3", "obj/4")),
new Tuple<>(app1ReadWrite, Sets.newHashSet("obj/5")),
new Tuple<>(app1All, Sets.newHashSet("obj/6", "obj/7")),
new Tuple<>(app2Read, Sets.newHashSet("obj/1", "obj/8"))
));
assertThat(perm.getApplicationNames(), containsInAnyOrder("app1", "app2"));
assertThat(perm.getPrivileges("app1"), containsInAnyOrder(app1Read, app1Write, app1ReadWrite, app1All));
assertThat(perm.getPrivileges("app2"), containsInAnyOrder(app2Read));
assertThat(perm.getResourcePatterns(app1Read), containsInAnyOrder("obj/1", "obj/2", "obj/5", "obj/6", "obj/7"));
assertThat(perm.getResourcePatterns(app1Write), containsInAnyOrder("obj/3", "obj/4", "obj/5", "obj/6", "obj/7"));
assertThat(perm.getResourcePatterns(app1ReadWrite), containsInAnyOrder("obj/5", "obj/6", "obj/7"));
assertThat(perm.getResourcePatterns(app1All), containsInAnyOrder("obj/6", "obj/7"));
assertThat(perm.getResourcePatterns(app2Read), containsInAnyOrder("obj/1", "obj/8"));
}
private ApplicationPrivilege actionPrivilege(String appName, String... actions) {
return ApplicationPrivilege.get(appName, Sets.newHashSet(actions), Collections.emptyList());
}
private ApplicationPrivilege compositePrivilege(String application, ApplicationPrivilege... children) {
Set<String> names = Stream.of(children).map(ApplicationPrivilege::name).flatMap(Set::stream).collect(Collectors.toSet());
return ApplicationPrivilege.get(application, names, store);
}
private ApplicationPermission buildPermission(ApplicationPrivilege privilege, String... resources) {
return new ApplicationPermission(singletonList(new Tuple<>(privilege, Sets.newHashSet(resources))));
}

View File

@ -107,6 +107,7 @@ import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.core.security.action.user.DeleteUserAction;
import org.elasticsearch.xpack.core.security.action.user.GetUsersAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
import org.elasticsearch.xpack.core.security.action.user.SetEnabledAction;
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
@ -162,6 +163,7 @@ import org.elasticsearch.xpack.security.action.user.TransportChangePasswordActio
import org.elasticsearch.xpack.security.action.user.TransportDeleteUserAction;
import org.elasticsearch.xpack.security.action.user.TransportGetUsersAction;
import org.elasticsearch.xpack.security.action.user.TransportHasPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.TransportGetUserPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.TransportPutUserAction;
import org.elasticsearch.xpack.security.action.user.TransportSetEnabledAction;
import org.elasticsearch.xpack.security.audit.AuditTrail;
@ -206,6 +208,7 @@ import org.elasticsearch.xpack.security.rest.action.saml.RestSamlLogoutAction;
import org.elasticsearch.xpack.security.rest.action.saml.RestSamlPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.rest.action.user.RestChangePasswordAction;
import org.elasticsearch.xpack.security.rest.action.user.RestDeleteUserAction;
import org.elasticsearch.xpack.security.rest.action.user.RestGetUserPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
@ -704,6 +707,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new ActionHandler<>(AuthenticateAction.INSTANCE, TransportAuthenticateAction.class),
new ActionHandler<>(SetEnabledAction.INSTANCE, TransportSetEnabledAction.class),
new ActionHandler<>(HasPrivilegesAction.INSTANCE, TransportHasPrivilegesAction.class),
new ActionHandler<>(GetUserPrivilegesAction.INSTANCE, TransportGetUserPrivilegesAction.class),
new ActionHandler<>(GetRoleMappingsAction.INSTANCE, TransportGetRoleMappingsAction.class),
new ActionHandler<>(PutRoleMappingAction.INSTANCE, TransportPutRoleMappingAction.class),
new ActionHandler<>(DeleteRoleMappingAction.INSTANCE, TransportDeleteRoleMappingAction.class),
@ -753,6 +757,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new RestChangePasswordAction(settings, restController, securityContext.get(), getLicenseState()),
new RestSetEnabledAction(settings, restController, getLicenseState()),
new RestHasPrivilegesAction(settings, restController, securityContext.get(), getLicenseState()),
new RestGetUserPrivilegesAction(settings, restController, securityContext.get(), getLicenseState()),
new RestGetRoleMappingsAction(settings, restController, getLicenseState()),
new RestPutRoleMappingAction(settings, restController, getLicenseState()),
new RestDeleteRoleMappingAction(settings, restController, getLicenseState()),

View File

@ -0,0 +1,133 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.user;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import static org.elasticsearch.common.Strings.arrayToCommaDelimitedString;
/**
* Transport action for {@link GetUserPrivilegesAction}
*/
public class TransportGetUserPrivilegesAction extends HandledTransportAction<GetUserPrivilegesRequest, GetUserPrivilegesResponse> {
private final ThreadPool threadPool;
private final AuthorizationService authorizationService;
@Inject
public TransportGetUserPrivilegesAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, AuthorizationService authorizationService) {
super(settings, GetUserPrivilegesAction.NAME, transportService, actionFilters, GetUserPrivilegesRequest::new);
this.threadPool = threadPool;
this.authorizationService = authorizationService;
}
@Override
protected void doExecute(Task task, GetUserPrivilegesRequest request, ActionListener<GetUserPrivilegesResponse> listener) {
final String username = request.username();
final User user = Authentication.getAuthentication(threadPool.getThreadContext()).getUser();
if (user.principal().equals(username) == false) {
listener.onFailure(new IllegalArgumentException("users may only list the privileges of their own account"));
return;
}
authorizationService.roles(user, ActionListener.wrap(
role -> listener.onResponse(buildResponseObject(role)),
listener::onFailure));
}
// package protected for testing
GetUserPrivilegesResponse buildResponseObject(Role userRole) {
logger.trace(() -> new ParameterizedMessage("List privileges for role [{}]", arrayToCommaDelimitedString(userRole.names())));
// We use sorted sets for Strings because they will typically be small, and having a predictable order allows for simpler testing
final Set<String> cluster = new TreeSet<>();
// But we don't have a meaningful ordering for objects like ConditionalClusterPrivilege, so the tests work with "random" ordering
final Set<ConditionalClusterPrivilege> conditionalCluster = new HashSet<>();
for (Tuple<ClusterPrivilege, ConditionalClusterPrivilege> tup : userRole.cluster().privileges()) {
if (tup.v2() == null) {
if (ClusterPrivilege.NONE.equals(tup.v1()) == false) {
cluster.addAll(tup.v1().name());
}
} else {
conditionalCluster.add(tup.v2());
}
}
final Set<GetUserPrivilegesResponse.Indices> indices = new LinkedHashSet<>();
for (IndicesPermission.Group group : userRole.indices()) {
final Set<BytesReference> queries = group.getQuery() == null ? Collections.emptySet() : group.getQuery();
final Set<FieldPermissionsDefinition.FieldGrantExcludeGroup> fieldSecurity = group.getFieldPermissions().hasFieldLevelSecurity()
? group.getFieldPermissions().getFieldPermissionsDefinition().getFieldGrantExcludeGroups() : Collections.emptySet();
indices.add(new GetUserPrivilegesResponse.Indices(
Arrays.asList(group.indices()),
group.privilege().name(),
fieldSecurity,
queries
));
}
final Set<RoleDescriptor.ApplicationResourcePrivileges> application = new LinkedHashSet<>();
for (String applicationName : userRole.application().getApplicationNames()) {
for (ApplicationPrivilege privilege : userRole.application().getPrivileges(applicationName)) {
final Set<String> resources = userRole.application().getResourcePatterns(privilege);
if (resources.isEmpty()) {
logger.trace("No resources defined in application privilege {}", privilege);
} else {
application.add(RoleDescriptor.ApplicationResourcePrivileges.builder()
.application(applicationName)
.privileges(privilege.name())
.resources(resources)
.build());
}
}
}
final Privilege runAsPrivilege = userRole.runAs().getPrivilege();
final Set<String> runAs;
if (Operations.isEmpty(runAsPrivilege.getAutomaton())) {
runAs = Collections.emptySet();
} else {
runAs = runAsPrivilege.name();
}
return new GetUserPrivilegesResponse(cluster, conditionalCluster, indices, application, runAs);
}
}

View File

@ -41,6 +41,7 @@ import org.elasticsearch.transport.TransportActionProxy;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.core.security.action.user.UserRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
@ -86,7 +87,7 @@ public class AuthorizationService extends AbstractComponent {
private static final Predicate<String> MONITOR_INDEX_PREDICATE = IndexPrivilege.MONITOR.predicate();
private static final Predicate<String> SAME_USER_PRIVILEGE = Automatons.predicate(
ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME);
ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME);
private static final String INDEX_SUB_REQUEST_PRIMARY = IndexAction.NAME + "[p]";
private static final String INDEX_SUB_REQUEST_REPLICA = IndexAction.NAME + "[r]";
@ -522,7 +523,8 @@ public class AuthorizationService extends AbstractComponent {
return checkChangePasswordAction(authentication);
}
assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action) || sameUsername == false
assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action)
|| GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false
: "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
return sameUsername;
}

View File

@ -0,0 +1,85 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
import java.util.Collections;
import static org.elasticsearch.rest.RestRequest.Method.GET;
/**
* REST handler that list the privileges held by a user.
*/
public class RestGetUserPrivilegesAction extends SecurityBaseRestHandler {
private final SecurityContext securityContext;
public RestGetUserPrivilegesAction(Settings settings, RestController controller, SecurityContext securityContext,
XPackLicenseState licenseState) {
super(settings, licenseState);
this.securityContext = securityContext;
controller.registerHandler(GET, "/_xpack/security/user/_privileges", this);
}
@Override
public String getName() {
return "xpack_security_user_privileges_action";
}
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String username = securityContext.getUser().principal();
final GetUserPrivilegesRequestBuilder requestBuilder = new SecurityClient(client).prepareGetUserPrivileges(username);
return channel -> requestBuilder.execute(new RestListener(channel));
}
// Package protected for testing
static class RestListener extends RestBuilderListener<GetUserPrivilegesResponse> {
RestListener(RestChannel channel) {
super(channel);
}
@Override
public RestResponse buildResponse(GetUserPrivilegesResponse response, XContentBuilder builder) throws Exception {
builder.startObject();
builder.field(RoleDescriptor.Fields.CLUSTER.getPreferredName(), response.getClusterPrivileges());
builder.startArray(RoleDescriptor.Fields.GLOBAL.getPreferredName());
for (ConditionalClusterPrivilege ccp : response.getConditionalClusterPrivileges()) {
ConditionalClusterPrivileges.toXContent(builder, ToXContent.EMPTY_PARAMS, Collections.singleton(ccp));
}
builder.endArray();
builder.field(RoleDescriptor.Fields.INDICES.getPreferredName(), response.getIndexPrivileges());
builder.field(RoleDescriptor.Fields.APPLICATIONS.getPreferredName(), response.getApplicationPrivileges());
builder.field(RoleDescriptor.Fields.RUN_AS.getPreferredName(), response.getRunAs());
builder.endObject();
return new BytesRestResponse(RestStatus.OK, builder);
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.user;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.permission.Role;
import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges.ManageApplicationPrivileges;
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import java.util.Collections;
import java.util.Set;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.iterableWithSize;
import static org.mockito.Mockito.mock;
public class TransportGetUserPrivilegesActionTests extends ESTestCase {
public void testBuildResponseObject() {
final ManageApplicationPrivileges manageApplicationPrivileges = new ManageApplicationPrivileges(Sets.newHashSet("app01", "app02"));
final BytesArray query = new BytesArray("{\"term\":{\"public\":true}}");
final Role role = Role.builder("test", "role")
.cluster(Sets.newHashSet("monitor", "manage_watcher"), Collections.singleton(manageApplicationPrivileges))
.add(IndexPrivilege.get(Sets.newHashSet("read", "write")), "index-1")
.add(IndexPrivilege.ALL, "index-2", "index-3")
.add(
new FieldPermissions(new FieldPermissionsDefinition(new String[]{ "public.*" }, new String[0])),
Collections.singleton(query),
IndexPrivilege.READ, "index-4", "index-5")
.addApplicationPrivilege(new ApplicationPrivilege("app01", "read", "data:read"), Collections.singleton("*"))
.runAs(new Privilege(Sets.newHashSet("user01", "user02"), "user01", "user02"))
.build();
final TransportGetUserPrivilegesAction action = new TransportGetUserPrivilegesAction(Settings.EMPTY,
mock(ThreadPool.class), mock(TransportService.class), mock(ActionFilters.class), mock(AuthorizationService.class));
final GetUserPrivilegesResponse response = action.buildResponseObject(role);
assertThat(response.getClusterPrivileges(), containsInAnyOrder("monitor", "manage_watcher"));
assertThat(response.getConditionalClusterPrivileges(), containsInAnyOrder(manageApplicationPrivileges));
assertThat(response.getIndexPrivileges(), iterableWithSize(3));
final GetUserPrivilegesResponse.Indices index1 = findIndexPrivilege(response.getIndexPrivileges(), "index-1");
assertThat(index1.getIndices(), containsInAnyOrder("index-1"));
assertThat(index1.getPrivileges(), containsInAnyOrder("read", "write"));
assertThat(index1.getFieldSecurity(), emptyIterable());
assertThat(index1.getQueries(), emptyIterable());
final GetUserPrivilegesResponse.Indices index2 = findIndexPrivilege(response.getIndexPrivileges(), "index-2");
assertThat(index2.getIndices(), containsInAnyOrder("index-2", "index-3"));
assertThat(index2.getPrivileges(), containsInAnyOrder("all"));
assertThat(index2.getFieldSecurity(), emptyIterable());
assertThat(index2.getQueries(), emptyIterable());
final GetUserPrivilegesResponse.Indices index4 = findIndexPrivilege(response.getIndexPrivileges(), "index-4");
assertThat(index4.getIndices(), containsInAnyOrder("index-4", "index-5"));
assertThat(index4.getPrivileges(), containsInAnyOrder("read"));
assertThat(index4.getFieldSecurity(), containsInAnyOrder(
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{ "public.*" }, new String[0])));
assertThat(index4.getQueries(), containsInAnyOrder(query));
assertThat(response.getApplicationPrivileges(), containsInAnyOrder(
RoleDescriptor.ApplicationResourcePrivileges.builder().application("app01").privileges("read").resources("*").build())
);
assertThat(response.getRunAs(), containsInAnyOrder("user01", "user02"));
}
private GetUserPrivilegesResponse.Indices findIndexPrivilege(Set<GetUserPrivilegesResponse.Indices> indices, String name) {
return indices.stream().filter(i -> i.getIndices().contains(name)).findFirst().get();
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.hamcrest.Matchers;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
public class RestGetUserPrivilegesActionTests extends ESTestCase {
public void testBuildResponse() throws Exception {
final RestGetUserPrivilegesAction.RestListener listener = new RestGetUserPrivilegesAction.RestListener(null);
final Set<String> cluster = new LinkedHashSet<>(Arrays.asList("monitor", "manage_ml", "manage_watcher"));
final Set<ConditionalClusterPrivilege> conditionalCluster = Collections.singleton(
new ConditionalClusterPrivileges.ManageApplicationPrivileges(new LinkedHashSet<>(Arrays.asList("app01", "app02"))));
final Set<GetUserPrivilegesResponse.Indices> index = new LinkedHashSet<>(Arrays.asList(
new GetUserPrivilegesResponse.Indices(Arrays.asList("index-1", "index-2", "index-3-*"), Arrays.asList("read", "write"),
new LinkedHashSet<>(Arrays.asList(
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{ "public.*" }, new String[0]),
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{ "*" }, new String[]{ "private.*" })
)),
new LinkedHashSet<>(Arrays.asList(
new BytesArray("{ \"term\": { \"access\": \"public\" } }"),
new BytesArray("{ \"term\": { \"access\": \"standard\" } }")
))
),
new GetUserPrivilegesResponse.Indices(Arrays.asList("index-4"), Collections.singleton("all"),
Collections.emptySet(), Collections.emptySet()
)
));
final Set<ApplicationResourcePrivileges> application = Sets.newHashSet(
ApplicationResourcePrivileges.builder().application("app01").privileges("read", "write").resources("*").build(),
ApplicationResourcePrivileges.builder().application("app01").privileges("admin").resources("department/1").build(),
ApplicationResourcePrivileges.builder().application("app02").privileges("all").resources("tenant/42", "tenant/99").build()
);
final Set<String> runAs = new LinkedHashSet<>(Arrays.asList("app-user-*", "backup-user"));
final GetUserPrivilegesResponse response = new GetUserPrivilegesResponse(cluster, conditionalCluster, index, application, runAs);
XContentBuilder builder = jsonBuilder();
listener.buildResponse(response, builder);
String json = Strings.toString(builder);
assertThat(json, Matchers.equalTo("{" +
"\"cluster\":[\"monitor\",\"manage_ml\",\"manage_watcher\"]," +
"\"global\":[" +
"{\"application\":{\"manage\":{\"applications\":[\"app01\",\"app02\"]}}}" +
"]," +
"\"indices\":[" +
"{\"names\":[\"index-1\",\"index-2\",\"index-3-*\"]," +
"\"privileges\":[\"read\",\"write\"]," +
"\"field_security\":[" +
"{\"grant\":[\"public.*\"]}," +
"{\"grant\":[\"*\"],\"except\":[\"private.*\"]}" +
"]," +
"\"query\":[" +
"\"{ \\\"term\\\": { \\\"access\\\": \\\"public\\\" } }\"," +
"\"{ \\\"term\\\": { \\\"access\\\": \\\"standard\\\" } }\"" +
"]}," +
"{\"names\":[\"index-4\"],\"privileges\":[\"all\"]}" +
"]," +
"\"applications\":[" +
"{\"application\":\"app01\",\"privileges\":[\"read\",\"write\"],\"resources\":[\"*\"]}," +
"{\"application\":\"app01\",\"privileges\":[\"admin\"],\"resources\":[\"department/1\"]}," +
"{\"application\":\"app02\",\"privileges\":[\"all\"],\"resources\":[\"tenant/42\",\"tenant/99\"]}" +
"]," +
"\"run_as\":[\"app-user-*\",\"backup-user\"]" +
"}"
));
}
}

View File

@ -0,0 +1,13 @@
{
"xpack.security.get_user_privileges": {
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-user-privileges.html",
"methods": [ "GET" ],
"url": {
"path": "/_xpack/security/user/_privileges",
"paths": [ "/_xpack/security/user/_privileges" ],
"parts": { },
"params": {}
},
"body": null
}
}

View File

@ -109,6 +109,16 @@ teardown:
"application" : "app02",
"resources" : [ "thing/1" ],
"privileges" : [ "data:write/thing" ]
},
{
"application" : "app01",
"resources" : [ "foo" ],
"privileges" : [ "dne", "data:dne" ]
},
{
"application" : "app-dne",
"resources" : [ "bar" ],
"privileges" : [ "anything", "action:anything" ]
}
]
}
@ -120,12 +130,22 @@ teardown:
"*" : {
"action:login" : true,
"data:read/secrets" : true
},
"foo" : {
"dne" : true,
"data:dne" : true
}
},
"app02" : {
"thing/1" : {
"data:write/thing" : true
}
},
"app-dne" : {
"bar" : {
"anything" : true,
"action:anything" : true
}
}
}
}
} }

View File

@ -0,0 +1,304 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
# Create some privileges
- do:
xpack.security.put_privileges:
body: >
{
"test-app": {
"user": {
"actions": [ "action:login", "version:1.0.*" ]
},
"read": {
"actions": [ "data:read/*" ]
},
"write": {
"actions": [ "data:write/*" ]
}
}
}
# Store 2 test roles
- do:
xpack.security.put_role:
name: "test-role-1"
body: >
{
"cluster": [ "monitor" ],
"global": {
"application": {
"manage": {
"applications": [ "test-*" ]
}
}
},
"indices": [
{
"names": ["test-1-*"],
"privileges": ["read" ]
},
{
"names": ["test-2-*"],
"privileges": ["read"],
"field_security": {
"grant" : ["*"],
"except" : [ "secret-*", "private-*" ]
},
"query" : { "term" : { "test": true } }
},
{
"names": ["test-3-*", "test-4-*", "test-5-*" ],
"privileges": ["read"],
"field_security": {
"grant" : ["test-*"]
}
},
{
"names": ["test-6-*", "test-7-*" ],
"privileges": ["read"],
"query" : { "term" : { "test": true } }
}
],
"applications": [
{
"application": "test-app",
"privileges": ["user"],
"resources": ["*"]
},
{
"application": "test-app",
"privileges": ["read"],
"resources": ["object/1"]
}
],
"run_as": [ "test-*" ]
}
- do:
xpack.security.put_role:
name: "test-role-2"
body: >
{
"cluster": [ "manage", "manage_security" ],
"global": {
"application": {
"manage": {
"applications": [ "apps-*" ]
}
}
},
"indices": [
{
"names": [ "test-1-*", "test-9-*" ],
"privileges": ["all" ]
},
{
"names": ["test-2-*"],
"privileges": ["read"],
"field_security": {
"grant" : ["apps-*"]
},
"query" : { "term" : { "apps": true } }
},
{
"names": ["test-3-*", "test-6-*" ],
"privileges": ["read", "write" ]
},
{
"names": ["test-4-*"],
"privileges": ["read" ],
"field_security": {
"grant" : ["*"],
"except" : [ "private-*" ]
}
}
],
"applications": [
{
"application": "app-dne",
"privileges": ["all"],
"resources": ["*"]
},
{
"application": "test-app",
"privileges": ["dne"],
"resources": ["*"]
},
{
"application": "test-app",
"privileges": ["read"],
"resources": ["object/2"]
}
],
"run_as": [ "app-*" ]
}
# And a user for each role combination
- do:
xpack.security.put_user:
username: "test-1"
body: >
{
"password": "12345678",
"roles" : [ "test-role-1" ]
}
- do:
xpack.security.put_user:
username: "test-2"
body: >
{
"password": "12345678",
"roles" : [ "test-role-2" ]
}
- do:
xpack.security.put_user:
username: "test-3"
body: >
{
"password": "12345678",
"roles" : [ "test-role-1", "test-role-2" ]
}
---
teardown:
- do:
xpack.security.delete_privileges:
application: test-app
name: "user,read,write"
ignore: 404
- do:
xpack.security.delete_user:
username: "test-1"
ignore: 404
- do:
xpack.security.delete_user:
username: "test-2"
ignore: 404
- do:
xpack.security.delete_user:
username: "test-3"
ignore: 404
- do:
xpack.security.delete_role:
name: "test-role-1"
ignore: 404
- do:
xpack.security.delete_role:
name: "test-role-2"
ignore: 404
---
"Test get_user_privileges for single role":
- do:
headers: { Authorization: "Basic dGVzdC0xOjEyMzQ1Njc4" } # test-1
xpack.security.get_user_privileges: {}
- match: { "cluster" : [ "monitor" ] }
- length: { "global" : 1 }
- match: { "global.0.application.manage.applications" : [ "test-*" ]}
- length: { "indices" : 4 }
- contains: { "indices" : { "names" : [ "test-1-*" ], "privileges" : [ "read" ] } }
- contains: { "indices" : { "names" : [ "test-2-*" ], "privileges" : [ "read" ],
"field_security" : [ { "grant" : [ "*" ], "except" : [ "secret-*", "private-*" ] } ],
"query" : [ "{\"term\":{\"test\":true}}" ] }
}
- contains: { "indices" : { "names" : [ "test-3-*" , "test-4-*", "test-5-*" ], "privileges" : ["read"],
"field_security" : [ { "grant" : [ "test-*" ] } ] }
}
- contains: { "indices" : { "names" : [ "test-6-*" , "test-7-*" ], "privileges" : ["read"],
"query" : [ "{\"term\":{\"test\":true}}" ] }
}
- length: { "applications" : 2 }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "user" ], "resources" : [ "*" ] } }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "read" ], "resources" : [ "object/1" ] } }
- match: { "run_as" : [ "test-*" ] }
- do:
headers: { Authorization: "Basic dGVzdC0yOjEyMzQ1Njc4" } # test-2
xpack.security.get_user_privileges:
username: null
- match: { "cluster" : [ "manage", "manage_security" ] }
- length: { "global" : 1 }
- match: { "global.0.application.manage.applications" : [ "apps-*" ]}
- length: { "indices" : 4 }
- contains: { "indices" : { "names" : [ "test-1-*", "test-9-*" ], "privileges" : [ "all" ] } }
- contains: { "indices" : { "names" : [ "test-2-*" ], "privileges" : [ "read" ],
"field_security" : [ { "grant" : [ "apps-*" ] } ],
"query" : [ "{\"term\":{\"apps\":true}}" ] }
}
- contains: { "indices" : { "names" : [ "test-3-*", "test-6-*" ], "privileges" : ["read","write"] } }
- contains: { "indices" : { "names" : [ "test-4-*" ], "privileges" : ["read"],
"field_security" : [ { "grant" : [ "*" ], "except" : [ "private-*" ] } ] }
}
- length: { "applications" : 3 }
- contains: { "applications" : { "application" : "app-dne", "privileges" : [ "all" ], "resources" : [ "*" ] } }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "read" ], "resources" : [ "object/2" ] } }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "dne" ], "resources" : [ "*" ] } }
- match: { "run_as" : [ "app-*" ] }
---
"Test get_user_privileges for merged roles":
- do:
headers: { Authorization: "Basic dGVzdC0zOjEyMzQ1Njc4" } # test-3
xpack.security.get_user_privileges: {}
- match: { "cluster" : [ "manage", "manage_security", "monitor" ] }
- length: { "global" : 2 }
- contains: { "global" : { "application" : { "manage" : { "applications" : [ "test-*" ]} } } }
- contains: { "global" : { "application" : { "manage" : { "applications" : [ "apps-*" ]} } } }
- length: { "indices" : 7 }
- contains: { "indices" : { "names" : [ "test-1-*" ], "privileges" : [ "read" ] } }
- contains: { "indices" : { "names" : [ "test-2-*" ], "privileges" : [ "read" ],
"field_security" : [
{ "grant" : [ "*" ], "except" : [ "secret-*", "private-*" ] },
{ "grant" : [ "apps-*" ] }
],
"query" : [
"{\"term\":{\"test\":true}}",
"{\"term\":{\"apps\":true}}"
]
} }
- contains: { "indices" : { "names" : [ "test-3-*" , "test-4-*", "test-5-*" ], "privileges" : ["read"],
"field_security" : [ { "grant" : [ "test-*" ] } ] }
}
- contains: { "indices" : { "names" : [ "test-6-*" , "test-7-*" ], "privileges" : ["read"],
"query" : [ "{\"term\":{\"test\":true}}" ] }
}
- contains: { "indices" : { "names" : [ "test-1-*", "test-9-*" ], "privileges" : [ "all" ] } }
- contains: { "indices" : { "names" : [ "test-3-*", "test-6-*" ], "privileges" : ["read","write"] } }
- contains: { "indices" : { "names" : [ "test-4-*" ], "privileges" : ["read"],
"field_security" : [ { "grant" : [ "*" ], "except" : [ "private-*" ] } ] }
}
- length: { "applications" : 3 }
- contains: { "applications" : { "application" : "app-dne", "privileges" : [ "all" ], "resources" : [ "*" ] } }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "user", "dne" ], "resources" : [ "*" ] } }
- contains: { "applications" : { "application" : "test-app", "privileges" : [ "read" ], "resources" : [ "object/1", "object/2" ] } }
- match: { "run_as" : [ "app-*", "test-*" ] }