Security API to determine which (if any) of a specified set of index/cluster privileges are held by the current (runAs) user.
Intended for use by Kibana to distinguish between read/write and read-only users, but should be applicable to other uses cases also.

Closes: elastic/x-pack-elasticsearch#282

Original commit: elastic/x-pack-elasticsearch@8b4cfdb858
This commit is contained in:
Tim Vernum 2017-03-03 18:41:44 +11:00 committed by GitHub
parent c92562e9d9
commit b105118ef0
16 changed files with 1057 additions and 9 deletions

View File

@ -74,12 +74,14 @@ import org.elasticsearch.xpack.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.security.action.user.DeleteUserAction;
import org.elasticsearch.xpack.security.action.user.GetUsersAction;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.PutUserAction;
import org.elasticsearch.xpack.security.action.user.SetEnabledAction;
import org.elasticsearch.xpack.security.action.user.TransportAuthenticateAction;
import org.elasticsearch.xpack.security.action.user.TransportChangePasswordAction;
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.TransportPutUserAction;
import org.elasticsearch.xpack.security.action.user.TransportSetEnabledAction;
import org.elasticsearch.xpack.security.audit.AuditTrail;
@ -122,6 +124,7 @@ import org.elasticsearch.xpack.security.rest.action.role.RestPutRoleAction;
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.RestGetUsersAction;
import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
import org.elasticsearch.xpack.security.rest.action.user.RestSetEnabledAction;
import org.elasticsearch.xpack.security.transport.SecurityServerTransportInterceptor;
@ -515,7 +518,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin {
new ActionHandler<>(DeleteRoleAction.INSTANCE, TransportDeleteRoleAction.class),
new ActionHandler<>(ChangePasswordAction.INSTANCE, TransportChangePasswordAction.class),
new ActionHandler<>(AuthenticateAction.INSTANCE, TransportAuthenticateAction.class),
new ActionHandler<>(SetEnabledAction.INSTANCE, TransportSetEnabledAction.class));
new ActionHandler<>(SetEnabledAction.INSTANCE, TransportSetEnabledAction.class),
new ActionHandler<>(HasPrivilegesAction.INSTANCE, TransportHasPrivilegesAction.class));
}
@Override
@ -548,7 +552,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin {
new RestPutRoleAction(settings, restController),
new RestDeleteRoleAction(settings, restController),
new RestChangePasswordAction(settings, restController, securityContext.get()),
new RestSetEnabledAction(settings, restController));
new RestSetEnabledAction(settings, restController),
new RestHasPrivilegesAction(settings, restController, securityContext.get()));
}
@Override

View File

@ -0,0 +1,35 @@
/*
* 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 java.util.Collections;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
/**
* This action is testing whether a user has the specified
* {@link org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges privileges}
*/
public class HasPrivilegesAction extends Action<HasPrivilegesRequest, HasPrivilegesResponse, HasPrivilegesRequestBuilder> {
public static final HasPrivilegesAction INSTANCE = new HasPrivilegesAction();
public static final String NAME = "cluster:admin/xpack/security/user/has_privileges";
private HasPrivilegesAction() {
super(NAME);
}
@Override
public HasPrivilegesRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new HasPrivilegesRequestBuilder(client);
}
@Override
public HasPrivilegesResponse newResponse() {
return new HasPrivilegesResponse();
}
}

View File

@ -0,0 +1,101 @@
/*
* 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 java.io.IOException;
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 org.elasticsearch.xpack.security.authz.RoleDescriptor;
import static org.elasticsearch.action.ValidateActions.addValidationError;
/**
* A request for checking a user's privileges
*/
public class HasPrivilegesRequest extends ActionRequest implements UserRequest {
private String username;
private String[] clusterPrivileges;
private RoleDescriptor.IndicesPrivileges[] indexPrivileges;
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (clusterPrivileges == null) {
validationException = addValidationError("clusterPrivileges must not be null", validationException);
}
if (indexPrivileges == null) {
validationException = addValidationError("indexPrivileges must not be null", validationException);
}
if (clusterPrivileges != null && clusterPrivileges.length == 0 && indexPrivileges != null && indexPrivileges.length == 0) {
validationException = addValidationError("clusterPrivileges and indexPrivileges cannot both be empty",
validationException);
}
return validationException;
}
/**
* @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 };
}
public RoleDescriptor.IndicesPrivileges[] indexPrivileges() {
return indexPrivileges;
}
public String[] clusterPrivileges() {
return clusterPrivileges;
}
public void indexPrivileges(RoleDescriptor.IndicesPrivileges... privileges) {
this.indexPrivileges = privileges;
}
public void clusterPrivileges(String... privileges) {
this.clusterPrivileges = privileges;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
this.username = in.readString();
this.clusterPrivileges = in.readStringArray();
int indexSize = in.readVInt();
indexPrivileges = new RoleDescriptor.IndicesPrivileges[indexSize];
for (int i = 0; i < indexSize; i++) {
indexPrivileges[i] = RoleDescriptor.IndicesPrivileges.createFrom(in);
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(username);
out.writeStringArray(clusterPrivileges);
out.writeVInt(indexPrivileges.length);
for (RoleDescriptor.IndicesPrivileges priv : indexPrivileges) {
priv.writeTo(out);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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 java.io.IOException;
import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges;
/**
* Request builder for checking a user's privileges
*/
public class HasPrivilegesRequestBuilder
extends ActionRequestBuilder<HasPrivilegesRequest, HasPrivilegesResponse, HasPrivilegesRequestBuilder> {
public HasPrivilegesRequestBuilder(ElasticsearchClient client) {
super(client, HasPrivilegesAction.INSTANCE, new HasPrivilegesRequest());
}
/**
* Set the username of the user that should enabled or disabled. Must not be {@code null}
*/
public HasPrivilegesRequestBuilder username(String username) {
request.username(username);
return this;
}
/**
* Set whether the user should be enabled or not
*/
public HasPrivilegesRequestBuilder source(String username, BytesReference source, XContentType xContentType) throws IOException {
final RoleDescriptor role = RoleDescriptor.parsePrivilegesCheck(username + "/has_privileges", source, xContentType);
request.username(username);
request.indexPrivileges(role.getIndicesPrivileges());
request.clusterPrivileges(role.getClusterPrivileges());
return this;
}
}

View File

@ -0,0 +1,120 @@
/*
* 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 java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
/**
* Response for a {@link HasPrivilegesRequest}
*/
public class HasPrivilegesResponse extends ActionResponse {
private boolean completeMatch;
private Map<String, Boolean> cluster;
private List<IndexPrivileges> index;
public HasPrivilegesResponse() {
this(true, Collections.emptyMap(), Collections.emptyList());
}
public HasPrivilegesResponse(boolean completeMatch, Map<String, Boolean> cluster, Collection<IndexPrivileges> index) {
super();
this.completeMatch = completeMatch;
this.cluster = new HashMap<>(cluster);
this.index = new ArrayList<>(index);
}
public boolean isCompleteMatch() {
return completeMatch;
}
public Map<String, Boolean> getClusterPrivileges() {
return Collections.unmodifiableMap(cluster);
}
public List<IndexPrivileges> getIndexPrivileges() {
return Collections.unmodifiableList(index);
}
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
completeMatch = in.readBoolean();
int count = in.readVInt();
index = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
final String index = in.readString();
final Map<String, Boolean> privileges = in.readMap(StreamInput::readString, StreamInput::readBoolean);
this.index.add(new IndexPrivileges(index, privileges));
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeBoolean(completeMatch);
out.writeVInt(index.size());
for (IndexPrivileges index : index) {
out.writeString(index.index);
out.writeMap(index.privileges, StreamOutput::writeString, StreamOutput::writeBoolean);
}
}
public static class IndexPrivileges {
private final String index;
private final Map<String, Boolean> privileges;
public IndexPrivileges(String index, Map<String, Boolean> privileges) {
this.index = Objects.requireNonNull(index);
this.privileges = Collections.unmodifiableMap(privileges);
}
public String getIndex() {
return index;
}
public Map<String, Boolean> getPrivileges() {
return privileges;
}
@Override
public String toString() {
return getClass().getSimpleName() + "{" +
"index='" + index + '\'' +
", privileges=" + privileges +
'}';
}
@Override
public int hashCode() {
int result = index.hashCode();
result = 31 * result + privileges.hashCode();
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final IndexPrivileges other = (IndexPrivileges) o;
return this.index.equals(other.index) && this.privileges.equals(other.privileges);
}
}
}

View File

@ -0,0 +1,132 @@
/*
* 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.apache.lucene.util.automaton.Automaton;
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.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.permission.IndicesPermission;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.Privilege;
import org.elasticsearch.xpack.security.support.Automatons;
import org.elasticsearch.xpack.security.user.User;
/**
* Transport action that tests whether a user has the specified
* {@link org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges privileges}
*/
public class TransportHasPrivilegesAction extends HandledTransportAction<HasPrivilegesRequest, HasPrivilegesResponse> {
private final AuthorizationService authorizationService;
@Inject
public TransportHasPrivilegesAction(Settings settings, ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
AuthorizationService authorizationService) {
super(settings, HasPrivilegesAction.NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver,
HasPrivilegesRequest::new);
this.authorizationService = authorizationService;
}
@Override
protected void doExecute(HasPrivilegesRequest request, ActionListener<HasPrivilegesResponse> listener) {
final String username = request.username();
final User runAsUser = Authentication.getAuthentication(threadPool.getThreadContext()).getRunAsUser();
if (runAsUser.principal().equals(username) == false) {
listener.onFailure(new IllegalArgumentException("users may only check the privileges of their own account"));
return;
}
authorizationService.roles(runAsUser, ActionListener.wrap(
role -> checkPrivileges(request, role, listener),
listener::onFailure));
}
private void checkPrivileges(HasPrivilegesRequest request, Role userRole,
ActionListener<HasPrivilegesResponse> listener) {
if (logger.isDebugEnabled()) {
logger.debug("Check whether role [{}] has privileges cluster=[{}] index=[{}]", userRole.name(),
Arrays.toString(request.clusterPrivileges()), Arrays.toString(request.indexPrivileges()));
}
Map<String, Boolean> cluster = new HashMap<>();
for (String checkAction : request.clusterPrivileges()) {
final ClusterPrivilege checkPrivilege = ClusterPrivilege.get(Collections.singleton(checkAction));
final ClusterPrivilege rolePrivilege = userRole.cluster().privilege();
cluster.put(checkAction, testPrivilege(checkPrivilege, rolePrivilege.getAutomaton()));
}
final Map<IndicesPermission.Group, Predicate<String>> predicateCache = new HashMap<>();
final Map<String, HasPrivilegesResponse.IndexPrivileges> indices = new LinkedHashMap<>();
boolean allMatch = true;
for (RoleDescriptor.IndicesPrivileges check : request.indexPrivileges()) {
for (String index : check.getIndices()) {
final Map<String, Boolean> privileges = new HashMap<>();
final HasPrivilegesResponse.IndexPrivileges existing = indices.get(index);
if (existing != null) {
privileges.putAll(existing.getPrivileges());
}
for (String privilege : check.getPrivileges()) {
if (testIndexMatch(index, privilege, userRole, predicateCache)) {
logger.debug("Role [{}] has [{}] on [{}]", userRole.name(), privilege, index);
privileges.put(privilege, true);
} else {
logger.debug("Role [{}] does not have [{}] on [{}]", userRole.name(), privilege, index);
privileges.put(privilege, false);
allMatch = false;
}
}
indices.put(index, new HasPrivilegesResponse.IndexPrivileges(index, privileges));
}
}
listener.onResponse(new HasPrivilegesResponse(allMatch, cluster, indices.values()));
}
private boolean testIndexMatch(String checkIndex, String checkPrivilegeName, Role userRole,
Map<IndicesPermission.Group, Predicate<String>> predicateCache) {
final IndexPrivilege checkPrivilege = IndexPrivilege.get(Collections.singleton(checkPrivilegeName));
List<Automaton> privilegeAutomatons = new ArrayList<>();
for (IndicesPermission.Group group : userRole.indices().groups()) {
final Predicate<String> predicate = predicateCache.computeIfAbsent(group, g -> Automatons.predicate(g.indices()));
if (predicate.test(checkIndex)) {
final IndexPrivilege rolePrivilege = group.privilege();
if (rolePrivilege.name().contains(checkPrivilegeName)) {
return true;
}
privilegeAutomatons.add(rolePrivilege.getAutomaton());
}
}
return testPrivilege(checkPrivilege, Automatons.unionAndMinimize(privilegeAutomatons));
}
private boolean testPrivilege(Privilege checkPrivilege, Automaton roleAutomaton) {
return Operations.subsetOf(checkPrivilege.getAutomaton(), roleAutomaton);
}
}

View File

@ -36,6 +36,7 @@ import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.UserRequest;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.Authentication;
@ -74,7 +75,8 @@ public class AuthorizationService extends AbstractComponent {
public static final String ORIGINATING_ACTION_KEY = "_originating_action_name";
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);
private static final Predicate<String> SAME_USER_PRIVILEGE = Automatons.predicate(
ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME);
private static final String INDEX_SUB_REQUEST_PRIMARY = IndexAction.NAME + "[p]";
private static final String INDEX_SUB_REQUEST_REPLICA = IndexAction.NAME + "[r]";
@ -374,7 +376,8 @@ public class AuthorizationService extends AbstractComponent {
return checkChangePasswordAction(authentication);
}
assert AuthenticateAction.NAME.equals(action) || sameUsername == false;
assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action) || sameUsername == false
: "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
return sameUsername;
}
return false;

View File

@ -25,6 +25,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import org.elasticsearch.xpack.security.support.Validation;
@ -231,7 +232,7 @@ public class RoleDescriptor implements ToXContentObject {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (Fields.INDICES.match(currentFieldName)) {
} else if (Fields.INDEX.match(currentFieldName) || Fields.INDICES.match(currentFieldName)) {
indicesPrivileges = parseIndices(name, parser, allow2xFormat);
} else if (Fields.RUN_AS.match(currentFieldName)) {
runAsUsers = readStringArray(name, parser, true);
@ -267,6 +268,47 @@ public class RoleDescriptor implements ToXContentObject {
}
}
public static RoleDescriptor parsePrivilegesCheck(String description, BytesReference source, XContentType xContentType)
throws IOException {
try (XContentParser parser = xContentType.xContent().createParser(NamedXContentRegistry.EMPTY, source)) {
// advance to the START_OBJECT token
XContentParser.Token token = parser.nextToken();
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. expected an object but found [{}] instead",
description, token);
}
String currentFieldName = null;
IndicesPrivileges[] indexPrivileges = null;
String[] clusterPrivileges = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (Fields.INDEX.match(currentFieldName)) {
indexPrivileges = parseIndices(description, parser, false);
} else if (Fields.CLUSTER.match(currentFieldName)) {
clusterPrivileges = readStringArray(description, parser, true);
} else {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. unexpected field [{}]",
description, currentFieldName);
}
}
if (indexPrivileges == null && clusterPrivileges == null) {
throw new ElasticsearchParseException("failed to parse privileges check [{}]. fields [{}] and [{}] are both missing",
description, Fields.INDEX, Fields.CLUSTER);
}
if (indexPrivileges != null) {
if (Arrays.stream(indexPrivileges).anyMatch(IndicesPrivileges::isUsingFieldLevelSecurity)) {
throw new ElasticsearchParseException("Field [{}] is not supported in a has_privileges request",
RoleDescriptor.Fields.FIELD_PERMISSIONS);
}
if (Arrays.stream(indexPrivileges).anyMatch(IndicesPrivileges::isUsingDocumentLevelSecurity)) {
throw new ElasticsearchParseException("Field [{}] is not supported in a has_privileges request", Fields.QUERY);
}
}
return new RoleDescriptor(description, clusterPrivileges, indexPrivileges, null);
}
}
private static RoleDescriptor.IndicesPrivileges[] parseIndices(String roleName, XContentParser parser,
boolean allow2xFormat) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
@ -634,6 +676,7 @@ public class RoleDescriptor implements ToXContentObject {
public interface Fields {
ParseField CLUSTER = new ParseField("cluster");
ParseField INDEX = new ParseField("index");
ParseField INDICES = new ParseField("indices");
ParseField RUN_AS = new ParseField("run_as");
ParseField NAMES = new ParseField("names");

View File

@ -124,7 +124,7 @@ public final class IndexPrivilege extends Privilege {
} else if (indexPrivilege != null) {
automata.add(indexPrivilege.automaton);
} else {
throw new IllegalArgumentException("unknown index privilege [" + name + "]. a privilege must be either " +
throw new IllegalArgumentException("unknown index privilege [" + part + "]. a privilege must be either " +
"one of the predefined fixed indices privileges [" +
Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "] or a pattern over one of the available index" +
" actions");
@ -135,7 +135,7 @@ public final class IndexPrivilege extends Privilege {
if (actions.isEmpty() == false) {
automata.add(patterns(actions));
}
return new IndexPrivilege(name, Automatons.unionAndMinimize(automata));
return new IndexPrivilege(name, unionAndMinimize(automata));
}
static Map<String, IndexPrivilege> values() {

View File

@ -42,6 +42,10 @@ import org.elasticsearch.xpack.security.action.user.GetUsersAction;
import org.elasticsearch.xpack.security.action.user.GetUsersRequest;
import org.elasticsearch.xpack.security.action.user.GetUsersRequestBuilder;
import org.elasticsearch.xpack.security.action.user.GetUsersResponse;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesAction;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesRequest;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesRequestBuilder;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.security.action.user.PutUserAction;
import org.elasticsearch.xpack.security.action.user.PutUserRequest;
import org.elasticsearch.xpack.security.action.user.PutUserRequestBuilder;
@ -126,6 +130,22 @@ public class SecurityClient {
return client.execute(ClearRolesCacheAction.INSTANCE, request);
}
/**
* Permissions / Privileges
*/
public HasPrivilegesRequestBuilder prepareHasPrivileges(String username) {
return new HasPrivilegesRequestBuilder(client).username(username);
}
public HasPrivilegesRequestBuilder prepareHasPrivileges(String username, BytesReference source, XContentType xContentType)
throws IOException {
return new HasPrivilegesRequestBuilder(client).source(username, source, xContentType);
}
public void hasPrivileges(HasPrivilegesRequest request, ActionListener<HasPrivilegesResponse> listener) {
client.execute(HasPrivilegesAction.INSTANCE, request, listener);
}
/** User Management */
public GetUsersRequestBuilder prepareGetUsers(String... usernames) {

View File

@ -0,0 +1,97 @@
/*
* 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 java.io.IOException;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.BaseRestHandler;
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.security.SecurityContext;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesRequestBuilder;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.security.client.SecurityClient;
import org.elasticsearch.xpack.security.user.User;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestRequest.Method.POST;
/**
* REST handler that tests whether a user has the specified
* {@link org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges privileges}
*/
public class RestHasPrivilegesAction extends BaseRestHandler {
private final SecurityContext securityContext;
public RestHasPrivilegesAction(Settings settings, RestController controller, SecurityContext securityContext) {
super(settings);
this.securityContext = securityContext;
controller.registerHandler(GET, "/_xpack/security/user/{username}/_has_privileges", this);
controller.registerHandler(POST, "/_xpack/security/user/{username}/_has_privileges", this);
controller.registerHandler(GET, "/_xpack/security/user/_has_privileges", this);
controller.registerHandler(POST, "/_xpack/security/user/_has_privileges", this);
}
@Override
public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
final String username = getUsername(request);
HasPrivilegesRequestBuilder requestBuilder = new SecurityClient(client)
.prepareHasPrivileges(username, request.content(), request.getXContentType());
return channel -> requestBuilder.execute(new HasPrivilegesRestResponseBuilder(username, channel));
}
private String getUsername(RestRequest request) {
final String username = request.param("username");
if (username != null) {
return username;
}
final User user = securityContext.getUser();
if (user.runAs() != null) {
return user.runAs().principal();
} else {
return user.principal();
}
}
static class HasPrivilegesRestResponseBuilder extends RestBuilderListener<HasPrivilegesResponse> {
private String username;
HasPrivilegesRestResponseBuilder(String username, RestChannel channel) {
super(channel);
this.username = username;
}
@Override
public RestResponse buildResponse(HasPrivilegesResponse response, XContentBuilder builder) throws Exception {
builder.startObject()
.field("username", username)
.field("has_all_requested", response.isCompleteMatch());
builder.field("cluster");
builder.map(response.getClusterPrivileges());
builder.startObject("index");
for (HasPrivilegesResponse.IndexPrivileges index : response.getIndexPrivileges()) {
builder.field(index.getIndex());
builder.map(index.getPrivileges());
}
builder.endObject();
builder.endObject();
return new BytesRestResponse(RestStatus.OK, builder);
}
}
}

View File

@ -0,0 +1,117 @@
/*
* 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 java.nio.charset.StandardCharsets;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction;
import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
public class HasPrivilegesRequestBuilderTests extends ESTestCase {
public void testParseValidJsonWithClusterAndIndexPrivileges() throws Exception {
String json = "{ "
+ " \"cluster\":[ \"all\"],"
+ " \"index\":[ "
+ " { \"names\": [ \".kibana\", \".reporting\" ], "
+ " \"privileges\" : [ \"read\", \"write\" ] }, "
+ " { \"names\": [ \".security\" ], "
+ " \"privileges\" : [ \"manage\" ] } "
+ " ]"
+ "}";
final HasPrivilegesRequestBuilder builder = new HasPrivilegesRequestBuilder(mock(Client.class));
builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
final HasPrivilegesRequest request = builder.request();
assertThat(request.clusterPrivileges().length, equalTo(1));
assertThat(request.clusterPrivileges()[0], equalTo("all"));
assertThat(request.indexPrivileges().length, equalTo(2));
final RoleDescriptor.IndicesPrivileges privileges0 = request.indexPrivileges()[0];
assertThat(privileges0.getIndices(), arrayContaining(".kibana", ".reporting"));
assertThat(privileges0.getPrivileges(), arrayContaining("read", "write"));
final RoleDescriptor.IndicesPrivileges privileges1 = request.indexPrivileges()[1];
assertThat(privileges1.getIndices(), arrayContaining(".security"));
assertThat(privileges1.getPrivileges(), arrayContaining("manage"));
}
public void testParseValidJsonWithJustIndexPrivileges() throws Exception {
String json = "{ \"index\":[ "
+ "{ \"names\": [ \".kibana\", \".reporting\" ], "
+ " \"privileges\" : [ \"read\", \"write\" ] }, "
+ "{ \"names\": [ \".security\" ], "
+ " \"privileges\" : [ \"manage\" ] } "
+ "] }";
final HasPrivilegesRequestBuilder builder = new HasPrivilegesRequestBuilder(mock(Client.class));
builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
final HasPrivilegesRequest request = builder.request();
assertThat(request.clusterPrivileges().length, equalTo(0));
assertThat(request.indexPrivileges().length, equalTo(2));
final RoleDescriptor.IndicesPrivileges privileges0 = request.indexPrivileges()[0];
assertThat(privileges0.getIndices(), arrayContaining(".kibana", ".reporting"));
assertThat(privileges0.getPrivileges(), arrayContaining("read", "write"));
final RoleDescriptor.IndicesPrivileges privileges1 = request.indexPrivileges()[1];
assertThat(privileges1.getIndices(), arrayContaining(".security"));
assertThat(privileges1.getPrivileges(), arrayContaining("manage"));
}
public void testParseValidJsonWithJustClusterPrivileges() throws Exception {
String json = "{ \"cluster\":[ "
+ "\"manage\","
+ "\"" + ClusterHealthAction.NAME + "\","
+ "\"" + ClusterStatsAction.NAME + "\""
+ "] }";
final HasPrivilegesRequestBuilder builder = new HasPrivilegesRequestBuilder(mock(Client.class));
builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON);
final HasPrivilegesRequest request = builder.request();
assertThat(request.indexPrivileges().length, equalTo(0));
assertThat(request.clusterPrivileges(), arrayContaining("manage", ClusterHealthAction.NAME, ClusterStatsAction.NAME));
}
public void testUseOfFieldLevelSecurityThrowsException() throws Exception {
String json = "{ \"index\":[ "
+ "{"
+ " \"names\": [ \"employees\" ], "
+ " \"privileges\" : [ \"read\", \"write\" ] ,"
+ " \"field_security\": { \"grant\": [ \"name\", \"department\", \"title\" ] }"
+ "} ] }";
final HasPrivilegesRequestBuilder builder = new HasPrivilegesRequestBuilder(mock(Client.class));
final ElasticsearchParseException parseException = expectThrows(ElasticsearchParseException.class,
() -> builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON)
);
assertThat(parseException.getMessage(), containsString("[field_security]"));
}
public void testMissingPrivilegesThrowsException() throws Exception {
String json = "{ }";
final HasPrivilegesRequestBuilder builder = new HasPrivilegesRequestBuilder(mock(Client.class));
final ElasticsearchParseException parseException = expectThrows(ElasticsearchParseException.class,
() -> builder.source("elastic", new BytesArray(json.getBytes(StandardCharsets.UTF_8)), XContentType.JSON)
);
assertThat(parseException.getMessage(), containsString("[index] and [cluster] are both missing"));
}
}

View File

@ -0,0 +1,262 @@
/*
* 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 java.util.Collections;
import java.util.LinkedHashMap;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction;
import org.elasticsearch.action.delete.DeleteAction;
import org.elasticsearch.action.index.IndexAction;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.mock.orig.Mockito;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse.IndexPrivileges;
import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.security.user.User;
import org.hamcrest.Matchers;
import org.junit.Before;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TransportHasPrivilegesActionTests extends ESTestCase {
private User user;
private Role role;
private TransportHasPrivilegesAction action;
@Before
public void setup() {
final Settings settings = Settings.builder().build();
user = new User(randomAsciiOfLengthBetween(4, 12));
final ThreadPool threadPool = mock(ThreadPool.class);
final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
final TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService
.NOOP_TRANSPORT_INTERCEPTOR,
x -> null, null);
final Authentication authentication = mock(Authentication.class);
threadContext.putTransient(Authentication.AUTHENTICATION_KEY, authentication);
when(threadPool.getThreadContext()).thenReturn(threadContext);
when(authentication.getRunAsUser()).thenReturn(user);
AuthorizationService authorizationService = mock(AuthorizationService.class);
Mockito.doAnswer(invocationOnMock -> {
ActionListener<Role> listener = (ActionListener<Role>) invocationOnMock.getArguments()[1];
listener.onResponse(role);
return null;
}).when(authorizationService).roles(eq(user), any(ActionListener.class));
action = new TransportHasPrivilegesAction(settings, threadPool, transportService,
mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), authorizationService);
}
/**
* This tests that action names in the request are considered "matched" by the relevant named privilege
* (in this case that {@link DeleteAction} and {@link IndexAction} are satisfied by {@link IndexPrivilege#WRITE}).
*/
public void testNamedIndexPrivilegesMatchApplicableActions() throws Exception {
role = Role.builder("test1").cluster(ClusterPrivilege.ALL).add(IndexPrivilege.WRITE, "academy").build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges(ClusterHealthAction.NAME);
request.indexPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices("academy")
.privileges(DeleteAction.NAME, IndexAction.NAME)
.build());
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(request, future);
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(true));
assertThat(response.getClusterPrivileges().size(), equalTo(1));
assertThat(response.getClusterPrivileges().get(ClusterHealthAction.NAME), equalTo(true));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(1));
final IndexPrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getIndex(), equalTo("academy"));
assertThat(result.getPrivileges().size(), equalTo(2));
assertThat(result.getPrivileges().get(DeleteAction.NAME), equalTo(true));
assertThat(result.getPrivileges().get(IndexAction.NAME), equalTo(true));
}
/**
* This tests that the action responds correctly when the user/role has some, but not all
* of the privileges being checked.
*/
public void testMatchSubsetOfPrivileges() throws Exception {
role = Role.builder("test2")
.cluster(ClusterPrivilege.MONITOR)
.add(IndexPrivilege.INDEX, "academy")
.add(IndexPrivilege.WRITE, "initiative")
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges("monitor", "manage");
request.indexPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices("academy", "initiative", "school")
.privileges("delete", "index", "manage")
.build());
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(request, future);
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getClusterPrivileges().size(), equalTo(2));
assertThat(response.getClusterPrivileges().get("monitor"), equalTo(true));
assertThat(response.getClusterPrivileges().get("manage"), equalTo(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(3));
final IndexPrivileges academy = response.getIndexPrivileges().get(0);
final IndexPrivileges initiative = response.getIndexPrivileges().get(1);
final IndexPrivileges school = response.getIndexPrivileges().get(2);
assertThat(academy.getIndex(), equalTo("academy"));
assertThat(academy.getPrivileges().size(), equalTo(3));
assertThat(academy.getPrivileges().get("index"), equalTo(true)); // explicit
assertThat(academy.getPrivileges().get("delete"), equalTo(false));
assertThat(academy.getPrivileges().get("manage"), equalTo(false));
assertThat(initiative.getIndex(), equalTo("initiative"));
assertThat(initiative.getPrivileges().size(), equalTo(3));
assertThat(initiative.getPrivileges().get("index"), equalTo(true)); // implied by write
assertThat(initiative.getPrivileges().get("delete"), equalTo(true)); // implied by write
assertThat(initiative.getPrivileges().get("manage"), equalTo(false));
assertThat(school.getIndex(), equalTo("school"));
assertThat(school.getPrivileges().size(), equalTo(3));
assertThat(school.getPrivileges().get("index"), equalTo(false));
assertThat(school.getPrivileges().get("delete"), equalTo(false));
assertThat(school.getPrivileges().get("manage"), equalTo(false));
}
/**
* This tests that the action responds correctly when the user/role has none
* of the privileges being checked.
*/
public void testMatchNothing() throws Exception {
role = Role.builder("test3")
.cluster(ClusterPrivilege.MONITOR)
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges(Strings.EMPTY_ARRAY);
request.indexPrivileges(RoleDescriptor.IndicesPrivileges.builder()
.indices("academy")
.privileges("read", "write")
.build());
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(request, future);
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(1));
final IndexPrivileges result = response.getIndexPrivileges().get(0);
assertThat(result.getIndex(), equalTo("academy"));
assertThat(result.getPrivileges().size(), equalTo(2));
assertThat(result.getPrivileges().get("read"), equalTo(false));
assertThat(result.getPrivileges().get("write"), equalTo(false));
}
/**
* We intentionally ignore wildcards in the request. This tests that
* <code>log*</code> in the request isn't granted by <code>logstash-*</code>
* in the role, but <code>logstash-2016-*</code> is, because it's just
* treated as the name of an index.
*/
public void testWildcardsInRequestAreIgnored() throws Exception {
role = Role.builder("test3")
.add(IndexPrivilege.ALL, "logstash-*")
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges(Strings.EMPTY_ARRAY);
request.indexPrivileges(
RoleDescriptor.IndicesPrivileges.builder()
.indices("logstash-2016-*")
.privileges("write")
.build(),
RoleDescriptor.IndicesPrivileges.builder()
.indices("log*")
.privileges("read")
.build()
);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(request, future);
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
new IndexPrivileges("logstash-2016-*", Collections.singletonMap("write", true)),
new IndexPrivileges("log*", Collections.singletonMap("read", false))
));
}
public void testCheckingIndexPermissionsDefinedOnDifferentPatterns() throws Exception {
role = Role.builder("test-write")
.add(IndexPrivilege.INDEX, "apache-*")
.add(IndexPrivilege.DELETE, "apache-2016-*")
.build();
final HasPrivilegesRequest request = new HasPrivilegesRequest();
request.username(user.principal());
request.clusterPrivileges(Strings.EMPTY_ARRAY);
request.indexPrivileges(
RoleDescriptor.IndicesPrivileges.builder()
.indices("apache-2016-12", "apache-2017-01")
.privileges("index", "delete")
.build()
);
final PlainActionFuture<HasPrivilegesResponse> future = new PlainActionFuture();
action.doExecute(request, future);
final HasPrivilegesResponse response = future.get();
assertThat(response, notNullValue());
assertThat(response.isCompleteMatch(), is(false));
assertThat(response.getIndexPrivileges(), Matchers.iterableWithSize(2));
assertThat(response.getIndexPrivileges(), containsInAnyOrder(
new IndexPrivileges("apache-2016-12",
MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("delete", true).map()),
new IndexPrivileges("apache-2017-01",
MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
.put("index", true).put("delete", false).map()
)
));
}
}

View File

@ -82,7 +82,7 @@ public class RoleDescriptorTests extends ESTestCase {
assertEquals(0, rd.getIndicesPrivileges().length);
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": \"idx1\", \"privileges\": [\"p1\", " +
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"index\": [{\"names\": \"idx1\", \"privileges\": [\"p1\", " +
"\"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"field_security\": " +
"{\"grant\": [\"f1\", \"f2\"]}}, {\"names\": " +
"\"idx2\", " +
@ -93,7 +93,7 @@ public class RoleDescriptorTests extends ESTestCase {
assertEquals(3, rd.getIndicesPrivileges().length);
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": " +
q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"index\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": " +
"[\"p1\", \"p2\"]}]}";
rd = RoleDescriptor.parse("test", new BytesArray(q), false, XContentType.JSON);
assertEquals("test", rd.getName());
@ -134,6 +134,18 @@ public class RoleDescriptorTests extends ESTestCase {
}
public void testParseEmptyQuery() throws Exception {
String json = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"index\": [{\"names\": [\"idx1\",\"idx2\"], " +
"\"privileges\": [\"p1\", \"p2\"], \"query\": \"\"}]}";
RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(json), false, XContentType.JSON);
assertEquals("test", rd.getName());
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
assertEquals(1, rd.getIndicesPrivileges().length);
assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices());
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
assertNull(rd.getIndicesPrivileges()[0].getQuery());
}
public void testParseEmptyQueryUsingDeprecatedIndicesField() throws Exception {
String json = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], " +
"\"privileges\": [\"p1\", \"p2\"], \"query\": \"\"}]}";
RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(json), false, XContentType.JSON);

View File

@ -0,0 +1,55 @@
/*
* 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 java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction.HasPrivilegesRestResponseBuilder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Mockito.mock;
public class HasPrivilegesRestResponseTests extends ESTestCase {
public void testBuildValidJsonResponse() throws Exception {
final HasPrivilegesRestResponseBuilder response = new HasPrivilegesRestResponseBuilder("daredevil", mock(RestChannel.class));
final HasPrivilegesResponse actionResponse = new HasPrivilegesResponse(false,
Collections.singletonMap("manage", true),
Arrays.asList(
new HasPrivilegesResponse.IndexPrivileges("staff",
MapBuilder.<String, Boolean>newMapBuilder(new LinkedHashMap<>())
.put("read", true).put("index", true).put("delete", false).put("manage", false).map()),
new HasPrivilegesResponse.IndexPrivileges("customers",
MapBuilder.<String, Boolean>newMapBuilder(new LinkedHashMap<>())
.put("read", true).put("index", true).put("delete", true).put("manage", false).map())
));
final XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent());
final RestResponse rest = response.buildResponse(actionResponse, builder);
assertThat(rest, instanceOf(BytesRestResponse.class));
final String json = rest.content().utf8ToString();
assertThat(json, equalTo("{" +
"\"username\":\"daredevil\"," +
"\"has_all_requested\":false," +
"\"cluster\":{\"manage\":true}," +
"\"index\":{" +
"\"staff\":{\"read\":true,\"index\":true,\"delete\":false,\"manage\":false}," +
"\"customers\":{\"read\":true,\"index\":true,\"delete\":true,\"manage\":false}" +
"}}"));
}
}

View File

@ -88,6 +88,7 @@ cluster:admin/xpack/security/user/put
cluster:admin/xpack/security/user/delete
cluster:admin/xpack/security/user/get
cluster:admin/xpack/security/user/set_enabled
cluster:admin/xpack/security/user/has_privileges
cluster:admin/xpack/security/role/put
cluster:admin/xpack/security/role/delete
cluster:admin/xpack/security/role/get