security: add metadata to roles

This commit adds the ability to define metadata for roles. This metadata is currently
only used for the API and to indicate that a role is reserved. We can continue passing
on the metadata as needed, when necessary.

Closes elastic/elasticsearch#2036

Original commit: elastic/x-pack-elasticsearch@8b5f606138
This commit is contained in:
jaymode 2016-06-01 09:53:35 -04:00
parent f42f8cf756
commit 5be3832889
16 changed files with 215 additions and 81 deletions

View File

@ -82,7 +82,8 @@ public class SecurityTemplateService extends AbstractComponent implements Cluste
if (securityIndexRouting == null) {
if (event.localNodeMaster()) {
ClusterState state = event.state();
// TODO for the future need to add some checking in the event the template needs to be updated...
// norelease we need to add some checking in the event the template needs to be updated and also the mappings need to be
// updated on index too!
IndexTemplateMetaData templateMeta = state.metaData().templates().get(SECURITY_TEMPLATE_NAME);
final boolean createTemplate = (templateMeta == null);

View File

@ -8,18 +8,19 @@ package org.elasticsearch.xpack.security.action.role;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.elasticsearch.common.Nullable;
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.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.action.ValidateActions.addValidationError;
@ -33,6 +34,7 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
private List<RoleDescriptor.IndicesPrivileges> indicesPrivileges = new ArrayList<>();
private String[] runAs = Strings.EMPTY_ARRAY;
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
private Map<String, Object> metadata;
public PutRoleRequest() {
}
@ -43,6 +45,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
if (name == null) {
validationException = addValidationError("role name is missing", validationException);
}
if (metadata != null && MetadataUtils.containsReservedMetadata(metadata)) {
validationException =
addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]", validationException);
}
return validationException;
}
@ -86,6 +92,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
return refreshPolicy;
}
public void metadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
public String name() {
return name;
}
@ -102,6 +112,10 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
return runAs;
}
public Map<String, Object> metadata() {
return metadata;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -114,6 +128,7 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
}
runAs = in.readStringArray();
refreshPolicy = RefreshPolicy.readFrom(in);
metadata = in.readMap();
}
@Override
@ -127,12 +142,14 @@ public class PutRoleRequest extends ActionRequest<PutRoleRequest> implements Wri
}
out.writeStringArray(runAs);
refreshPolicy.writeTo(out);
out.writeMap(metadata);
}
RoleDescriptor roleDescriptor() {
return new RoleDescriptor(name,
clusterPrivileges,
indicesPrivileges.toArray(new RoleDescriptor.IndicesPrivileges[indicesPrivileges.size()]),
runAs);
runAs,
metadata);
}
}

View File

@ -12,6 +12,8 @@ import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import java.util.Map;
/**
* Builder for requests to add a role to the administrative index
*/
@ -33,6 +35,7 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
request.cluster(descriptor.getClusterPrivileges());
request.addIndex(descriptor.getIndicesPrivileges());
request.runAs(descriptor.getRunAs());
request.metadata(descriptor.getMetadata());
return this;
}
@ -56,4 +59,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
request.addIndex(indices, privileges, fields, query);
return this;
}
public PutRoleRequestBuilder metadata(Map<String, Object> metadata) {
request.metadata(metadata);
return this;
}
}

View File

@ -22,12 +22,15 @@ import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.security.support.Validation;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import org.elasticsearch.xpack.common.xcontent.XContentUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A holder for a Role that contains user-readable information about the Role
@ -39,16 +42,26 @@ public class RoleDescriptor implements ToXContent {
private final String[] clusterPrivileges;
private final IndicesPrivileges[] indicesPrivileges;
private final String[] runAs;
private final Map<String, Object> metadata;
public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@Nullable String[] runAs) {
this(name, clusterPrivileges, indicesPrivileges, runAs, null);
}
public RoleDescriptor(String name,
@Nullable String[] clusterPrivileges,
@Nullable IndicesPrivileges[] indicesPrivileges,
@Nullable String[] runAs,
@Nullable Map<String, Object> metadata) {
this.name = name;
this.clusterPrivileges = clusterPrivileges != null ? clusterPrivileges : Strings.EMPTY_ARRAY;
this.indicesPrivileges = indicesPrivileges != null ? indicesPrivileges : IndicesPrivileges.NONE;
this.runAs = runAs != null ? runAs : Strings.EMPTY_ARRAY;
this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap();
}
public String getName() {
@ -67,6 +80,10 @@ public class RoleDescriptor implements ToXContent {
return this.runAs;
}
public Map<String, Object> getMetadata() {
return metadata;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Role[");
@ -77,6 +94,8 @@ public class RoleDescriptor implements ToXContent {
sb.append(group.toString()).append(",");
}
sb.append("], runAs=[").append(Strings.arrayToCommaDelimitedString(runAs));
sb.append("], metadata=[");
MetadataUtils.writeValue(sb, metadata);
sb.append("]]");
return sb.toString();
}
@ -91,6 +110,7 @@ public class RoleDescriptor implements ToXContent {
if (!name.equals(that.name)) return false;
if (!Arrays.equals(clusterPrivileges, that.clusterPrivileges)) return false;
if (!Arrays.equals(indicesPrivileges, that.indicesPrivileges)) return false;
if (!metadata.equals(that.getMetadata())) return false;
return Arrays.equals(runAs, that.runAs);
}
@ -100,16 +120,18 @@ public class RoleDescriptor implements ToXContent {
result = 31 * result + Arrays.hashCode(clusterPrivileges);
result = 31 * result + Arrays.hashCode(indicesPrivileges);
result = 31 * result + Arrays.hashCode(runAs);
result = 31 * result + metadata.hashCode();
return result;
}
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("cluster", (Object[]) clusterPrivileges);
builder.field("indices", (Object[]) indicesPrivileges);
builder.field(Fields.CLUSTER.getPreferredName(), (Object[]) clusterPrivileges);
builder.field(Fields.INDICES.getPreferredName(), (Object[]) indicesPrivileges);
if (runAs != null) {
builder.field("run_as", runAs);
builder.field(Fields.RUN_AS.getPreferredName(), runAs);
}
builder.field(Fields.METADATA.getPreferredName(), metadata);
return builder.endObject();
}
@ -122,7 +144,8 @@ public class RoleDescriptor implements ToXContent {
indicesPrivileges[i] = IndicesPrivileges.createFrom(in);
}
String[] runAs = in.readStringArray();
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs);
Map<String, Object> metadata = in.readMap();
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAs, metadata);
}
public static void writeTo(RoleDescriptor descriptor, StreamOutput out) throws IOException {
@ -133,6 +156,7 @@ public class RoleDescriptor implements ToXContent {
group.writeTo(out);
}
out.writeStringArray(descriptor.runAs);
out.writeMap(descriptor.metadata);
}
public static RoleDescriptor parse(String name, BytesReference source) throws IOException {
@ -160,6 +184,7 @@ public class RoleDescriptor implements ToXContent {
IndicesPrivileges[] indicesPrivileges = null;
String[] clusterPrivileges = null;
String[] runAsUsers = null;
Map<String, Object> metadata = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
@ -169,11 +194,17 @@ public class RoleDescriptor implements ToXContent {
runAsUsers = readStringArray(name, parser, true);
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.CLUSTER)) {
clusterPrivileges = readStringArray(name, parser, true);
} else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.METADATA)) {
if (token != XContentParser.Token.START_OBJECT) {
throw new ElasticsearchParseException(
"expected field [{}] to be of type object, but found [{}] instead", currentFieldName, token);
}
metadata = parser.map();
} else {
throw new ElasticsearchParseException("failed to parse role [{}]. unexpected field [{}]", name, currentFieldName);
}
}
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers);
return new RoleDescriptor(name, clusterPrivileges, indicesPrivileges, runAsUsers, metadata);
}
private static String[] readStringArray(String roleName, XContentParser parser, boolean allowNull) throws IOException {
@ -355,9 +386,7 @@ public class RoleDescriptor implements ToXContent {
this.indices = in.readStringArray();
this.fields = in.readOptionalStringArray();
this.privileges = in.readStringArray();
if (in.readBoolean()) {
this.query = new BytesArray(in.readByteArray());
}
this.query = in.readOptionalBytesReference();
}
@Override
@ -365,12 +394,7 @@ public class RoleDescriptor implements ToXContent {
out.writeStringArray(indices);
out.writeOptionalStringArray(fields);
out.writeStringArray(privileges);
if (query != null) {
out.writeBoolean(true);
out.writeByteArray(BytesReference.toBytes(query));
} else {
out.writeBoolean(false);
}
out.writeOptionalBytesReference(query);
}
public static class Builder {
@ -424,5 +448,6 @@ public class RoleDescriptor implements ToXContent {
ParseField QUERY = new ParseField("query");
ParseField PRIVILEGES = new ParseField("privileges");
ParseField FIELDS = new ParseField("fields");
ParseField METADATA = new ParseField("metadata");
}
}

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
import org.elasticsearch.xpack.security.support.MetadataUtils;
/**
*
@ -20,7 +21,8 @@ public class KibanaRole extends Role {
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build() };
public static final String NAME = "kibana";
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null);
public static final RoleDescriptor DESCRIPTOR =
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
public static final KibanaRole INSTANCE = new KibanaRole();
private KibanaRole() {

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
import org.elasticsearch.xpack.security.support.MetadataUtils;
public class KibanaUserRole extends Role {
@ -16,7 +17,8 @@ public class KibanaUserRole extends Role {
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete").build() };
public static final String NAME = "kibana_user";
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null);
public static final RoleDescriptor DESCRIPTOR =
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
public static final KibanaUserRole INSTANCE = new KibanaUserRole();
private KibanaUserRole() {

View File

@ -9,6 +9,7 @@ import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
import org.elasticsearch.xpack.security.support.MetadataUtils;
/**
*
@ -19,7 +20,8 @@ public class SuperuserRole extends Role {
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, new String[] { "all" },
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()},
new String[] { "*" });
new String[] { "*" },
MetadataUtils.DEFAULT_RESERVED_METADATA);
public static final SuperuserRole INSTANCE = new SuperuserRole();
private SuperuserRole() {

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.permission;
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name;
import org.elasticsearch.xpack.security.support.MetadataUtils;
/**
* Reserved role for the transport client
@ -17,7 +18,8 @@ public class TransportClientRole extends Role {
public static final String NAME = "transport_client";
private static final String[] CLUSTER_PRIVILEGES = new String[] { "transport_client" };
public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null);
public static final RoleDescriptor DESCRIPTOR =
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
public static final TransportClientRole INSTANCE = new TransportClientRole();
private TransportClientRole() {

View File

@ -0,0 +1,71 @@
/*
* 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.support;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
public class MetadataUtils {
public static final String RESERVED_PREFIX = "_";
public static final String RESERVED_METADATA_KEY = RESERVED_PREFIX + "reserved";
public static final Map<String, Object> DEFAULT_RESERVED_METADATA = Collections.singletonMap(RESERVED_METADATA_KEY, true);
private MetadataUtils() {}
public static void writeValue(StringBuilder sb, Object object) {
if (object instanceof Map) {
sb.append("{");
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
sb.append(entry.getKey()).append("=");
writeValue(sb, entry.getValue());
}
sb.append("}");
} else if (object instanceof Collection) {
sb.append("[");
boolean first = true;
for (Object item : (Collection) object) {
if (!first) {
sb.append(",");
}
writeValue(sb, item);
first = false;
}
sb.append("]");
} else if (object.getClass().isArray()) {
sb.append("[");
for (int i = 0; i < Array.getLength(object); i++) {
if (i != 0) {
sb.append(",");
}
writeValue(sb, Array.get(object, i));
}
sb.append("]");
} else {
sb.append(object);
}
}
public static void verifyNoReservedMetadata(Map<String, Object> metadata) {
for (String key : metadata.keySet()) {
if (key.startsWith(RESERVED_PREFIX)) {
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses");
}
}
}
public static boolean containsReservedMetadata(Map<String, Object> metadata) {
for (String key : metadata.keySet()) {
if (key.startsWith(RESERVED_PREFIX)) {
return true;
}
}
return false;
}
}

View File

@ -14,12 +14,11 @@ import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
@ -28,8 +27,6 @@ import java.util.Map;
*/
public class User implements ToXContent {
static final String RESERVED_PREFIX = "_";
private final String username;
private final String[] roles;
private final User runAs;
@ -53,7 +50,7 @@ public class User implements ToXContent {
this.fullName = fullName;
this.email = email;
this.runAs = null;
verifyNoReservedMetadata(this.username, this.metadata);
verifyNoReservedMetadata(this.metadata);
}
public User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, User runAs) {
@ -67,7 +64,7 @@ public class User implements ToXContent {
throw new ElasticsearchSecurityException("invalid run_as user");
}
this.runAs = runAs;
verifyNoReservedMetadata(this.username, this.metadata);
verifyNoReservedMetadata(this.metadata);
}
/**
@ -125,7 +122,7 @@ public class User implements ToXContent {
sb.append(",fullName=").append(fullName);
sb.append(",email=").append(email);
sb.append(",metadata=");
append(sb, metadata);
MetadataUtils.writeValue(sb, metadata);
if (runAs != null) {
sb.append(",runAs=[").append(runAs.toString()).append("]");
}
@ -172,16 +169,12 @@ public class User implements ToXContent {
return builder.endObject();
}
void verifyNoReservedMetadata(String principal, Map<String, Object> metadata) {
void verifyNoReservedMetadata(Map<String, Object> metadata) {
if (this instanceof ReservedUser) {
return;
}
for (String key : metadata.keySet()) {
if (key.startsWith(RESERVED_PREFIX)) {
throw new IllegalArgumentException("invalid user metadata. [" + key + "] is a reserved for internal uses");
}
}
MetadataUtils.verifyNoReservedMetadata(metadata);
}
public static User readFrom(StreamInput input) throws IOException {
@ -245,49 +238,10 @@ public class User implements ToXContent {
}
}
public static void append(StringBuilder sb, Object object) {
if (object == null) {
sb.append((Object) null);
}
if (object instanceof Map) {
sb.append("{");
for (Map.Entry<String, Object> entry : ((Map<String, Object>)object).entrySet()) {
sb.append(entry.getKey()).append("=");
append(sb, entry.getValue());
}
sb.append("}");
} else if (object instanceof Collection) {
sb.append("[");
boolean first = true;
for (Object item : (Collection) object) {
if (!first) {
sb.append(",");
}
append(sb, item);
first = false;
}
sb.append("]");
} else if (object.getClass().isArray()) {
sb.append("[");
for (int i = 0; i < Array.getLength(object); i++) {
if (i != 0) {
sb.append(",");
}
append(sb, Array.get(object, i));
}
sb.append("]");
} else {
sb.append(object);
}
}
abstract static class ReservedUser extends User {
private static final String RESERVED_KEY = User.RESERVED_PREFIX + "reserved";
ReservedUser(String username, String... roles) {
super(username, roles, null, null, Collections.singletonMap(RESERVED_KEY, true));
super(username, roles, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
}
}

View File

@ -86,6 +86,10 @@
},
"run_as" : {
"type" : "keyword"
},
"metadata" : {
"type" : "object",
"dynamic" : true
}
}
},

View File

@ -42,7 +42,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase {
RoleDescriptor rd = new RoleDescriptor("rolename", cluster, ips, runAs);
assertThat(ESNativeRealmMigrateTool.MigrateUserOrRoles.createRoleJson(rd),
equalTo("{\"cluster\":[],\"indices\":[{\"names\":[\"i1\",\"i2\",\"i3\"]," +
"\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[]}"));
"\"privileges\":[\"all\"],\"fields\":[\"body\"]}],\"run_as\":[],\"metadata\":{}}"));
}

View File

@ -55,7 +55,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
* Tests for the ESNativeUsersStore and ESNativeRolesStore
* Tests for the NativeUsersStore and NativeRolesStore
*/
public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
@ -144,12 +144,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
SecurityClient c = securityClient();
final List<RoleDescriptor> existingRoles = Arrays.asList(c.prepareGetRoles().get().roles());
final int existing = existingRoles.size();
final Map<String, Object> metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10));
logger.error("--> creating role");
c.preparePutRole("test_role")
.cluster("all", "none")
.runAs("root", "nobody")
.addIndices(new String[]{"index"}, new String[]{"read"},
new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}"))
.metadata(metadata)
.get();
logger.error("--> waiting for .security index");
ensureGreen(SecurityTemplateService.SECURITY_INDEX_NAME);
@ -158,6 +160,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
assertTrue("role should exist", resp.hasRoles());
RoleDescriptor testRole = resp.roles()[0];
assertNotNull(testRole);
assertThat(testRole.getMetadata().size(), is(1));
assertThat(testRole.getMetadata().get("key"), is(metadata.get("key")));
c.preparePutRole("test_role2")
.cluster("all", "none")

View File

@ -7,10 +7,17 @@ package org.elasticsearch.xpack.security.authz;
import org.elasticsearch.common.Strings;
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.StreamInput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import org.elasticsearch.test.ESTestCase;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.core.Is.is;
@ -37,7 +44,7 @@ public class RoleDescriptorTests extends ESTestCase {
};
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
assertThat(descriptor.toString(), is("Role[name=test, cluster=[all,none], indicesPrivileges=[IndicesPrivileges[indices=[i1,i2], " +
"privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo]]"));
"privileges=[read], fields=[body,title], query={\"query\": {\"match_all\": {}}}],], runAs=[sudo], metadata=[{}]]"));
}
public void testToXContent() throws Exception {
@ -49,7 +56,8 @@ public class RoleDescriptorTests extends ESTestCase {
.query("{\"query\": {\"match_all\": {}}}")
.build()
};
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" });
Map<String, Object> metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null;
RoleDescriptor descriptor = new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata);
XContentBuilder builder = descriptor.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS);
RoleDescriptor parsed = RoleDescriptor.parse("test", builder.bytes());
assertThat(parsed, is(descriptor));
@ -88,5 +96,34 @@ public class RoleDescriptorTests extends ESTestCase {
assertEquals(1, rd.getIndicesPrivileges().length);
assertArrayEquals(new String[] { "idx1", "idx2" }, rd.getIndicesPrivileges()[0].getIndices());
assertArrayEquals(new String[] { "m", "n" }, rd.getRunAs());
q = "{\"cluster\":[\"a\", \"b\"], \"metadata\":{\"foo\":\"bar\"}}";
rd = RoleDescriptor.parse("test", new BytesArray(q));
assertEquals("test", rd.getName());
assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges());
assertEquals(0, rd.getIndicesPrivileges().length);
assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs());
assertNotNull(rd.getMetadata());
assertThat(rd.getMetadata().size(), is(1));
assertThat(rd.getMetadata().get("foo"), is("bar"));
}
public void testSerialization() throws Exception {
BytesStreamOutput output = new BytesStreamOutput();
RoleDescriptor.IndicesPrivileges[] groups = new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()
.indices("i1", "i2")
.privileges("read")
.fields("body", "title")
.query("{\"query\": {\"match_all\": {}}}")
.build()
};
Map<String, Object> metadata = randomBoolean() ? MetadataUtils.DEFAULT_RESERVED_METADATA : null;
final RoleDescriptor descriptor =
new RoleDescriptor("test", new String[] { "all", "none" }, groups, new String[] { "sudo" }, metadata);
RoleDescriptor.writeTo(descriptor, output);
StreamInput streamInput = ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes()));
final RoleDescriptor serialized = RoleDescriptor.readFrom(streamInput);
assertEquals(descriptor, serialized);
}
}

View File

@ -7,10 +7,9 @@ package org.elasticsearch.xpack.security.user;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.xpack.security.support.MetadataUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.XPackClient;
import java.util.Arrays;
import java.util.Collections;
@ -127,7 +126,7 @@ public class UserTests extends ESTestCase {
public void testReservedMetadata() throws Exception {
Map<String, Object> validMetadata = Collections.singletonMap("foo", "bar");
Map<String, Object> invalidMetadata = Collections.singletonMap(User.RESERVED_PREFIX + "foo", "bar");
Map<String, Object> invalidMetadata = Collections.singletonMap(MetadataUtils.RESERVED_PREFIX + "foo", "bar");
IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () ->
new User("john", Strings.EMPTY_ARRAY, "John Doe", "john@doe.com", invalidMetadata));

View File

@ -38,6 +38,10 @@ teardown:
body: >
{
"cluster": ["all"],
"metadata": {
"key1" : "val1",
"key2" : "val2"
},
"indices": [
{
"names": "*",
@ -73,5 +77,7 @@ teardown:
xpack.security.get_role:
name: "admin_role"
- match: { admin_role.cluster.0: "all" }
- match: { admin_role.metadata.key1: "val1" }
- match: { admin_role.metadata.key2: "val2" }
- match: { admin_role.indices.0.names.0: "*" }
- match: { admin_role.indices.0.privileges.0: "all" }