Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get-user-privileges API #33928

Merged
merged 20 commits into from
Oct 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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 {
tvernum marked this conversation as resolved.
Show resolved Hide resolved
throw new UnsupportedOperationException("Use " + getClass() + " as Writeable not Streamable");
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(username);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
tvernum marked this conversation as resolved.
Show resolved Hide resolved

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)));
tvernum marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ public Builder privileges(String... privileges) {
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;
Expand Down Expand Up @@ -919,7 +923,7 @@ public Builder resources(String... resources) {
return this;
}

public Builder resources(List<String> resources) {
public Builder resources(Collection<String> resources) {
return resources(resources.toArray(new String[resources.size()]));
}

Expand Down
Loading