diff --git a/elasticsearch/qa/reindex-tests-with-security/roles.yml b/elasticsearch/qa/reindex-tests-with-security/roles.yml index 41bef8dc3a7..7b78871b47a 100644 --- a/elasticsearch/qa/reindex-tests-with-security/roles.yml +++ b/elasticsearch/qa/reindex-tests-with-security/roles.yml @@ -76,9 +76,10 @@ can_not_see_hidden_fields: - write - create_index - indices:admin/refresh - fields: - - foo - - bar + field_security: + grant: + - foo + - bar - names: dest privileges: - read diff --git a/elasticsearch/qa/security-migrate-tests/roles.yml b/elasticsearch/qa/security-migrate-tests/roles.yml index b55b208c3ed..94f67557620 100644 --- a/elasticsearch/qa/security-migrate-tests/roles.yml +++ b/elasticsearch/qa/security-migrate-tests/roles.yml @@ -5,9 +5,10 @@ actual_role: indices: - names: [ "index1", "index2" ] privileges: [ "read", "write", "create_index", "indices:admin/refresh" ] - fields: - - foo - - bar + field_security: + grant: + - foo + - bar query: bool: must_not: diff --git a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java index a1989873f09..8af7f56b5aa 100644 --- a/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java +++ b/elasticsearch/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java @@ -12,7 +12,6 @@ import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; import org.elasticsearch.common.Priority; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.xpack.security.action.role.GetRolesResponse; @@ -46,13 +45,6 @@ public class MigrateToolIT extends MigrateToolTestCase { assertTrue(pur.created()); } - private static String[] args(String command) { - if (!Strings.hasLength(command)) { - return Strings.EMPTY_ARRAY; - } - return command.split("\\s+"); - } - public void testRunMigrateTool() throws Exception { Settings settings = Settings.builder() .put("path.home", createTempDir().toAbsolutePath().toString()) @@ -100,13 +92,15 @@ public class MigrateToolIT extends MigrateToolTestCase { for (RoleDescriptor.IndicesPrivileges ip : ips) { if (Arrays.equals(ip.getIndices(), new String[]{"index1", "index2"})) { assertArrayEquals(ip.getPrivileges(), new String[]{"read", "write", "create_index", "indices:admin/refresh"}); - assertArrayEquals(ip.getFields(), new String[]{"foo", "bar"}); + assertTrue(ip.getFieldPermissions().hasFieldLevelSecurity()); + assertTrue(ip.getFieldPermissions().grantsAccessTo("bar")); + assertTrue(ip.getFieldPermissions().grantsAccessTo("foo")); assertNotNull(ip.getQuery()); assertThat(ip.getQuery().utf8ToString(), containsString("{\"bool\":{\"must_not\":{\"match\":{\"hidden\":true}}}}")); } else { assertArrayEquals(ip.getIndices(), new String[]{"*"}); assertArrayEquals(ip.getPrivileges(), new String[]{"read"}); - assertArrayEquals(ip.getFields(), null); + assertFalse(ip.getFieldPermissions().hasFieldLevelSecurity()); assertNull(ip.getQuery()); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java index 210b23bec90..4e7b335a74d 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java @@ -46,7 +46,7 @@ public class BulkRequestInterceptor extends AbstractComponent implements Request for (String index : indicesRequest.indices()) { IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index); if (indexAccessControl != null) { - boolean fls = indexAccessControl.getFields() != null; + boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); boolean dls = indexAccessControl.getQueries() != null; if (fls || dls) { if (indicesRequest instanceof UpdateRequest) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java index c4b0ef58682..1e490bcddcd 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java @@ -55,7 +55,7 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor extends for (String index : indicesRequest.indices()) { IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index); if (indexAccessControl != null) { - boolean fieldLevelSecurityEnabled = indexAccessControl.getFields() != null; + boolean fieldLevelSecurityEnabled = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); boolean documentLevelSecurityEnabled = indexAccessControl.getQueries() != null; if (fieldLevelSecurityEnabled || documentLevelSecurityEnabled) { if (fieldLevelSecurityEnabled || documentLevelSecurityEnabled) { diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java index 1b7a9b4bfc7..d5fa633d2ad 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java @@ -15,6 +15,7 @@ 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 org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.io.IOException; import java.util.ArrayList; @@ -64,11 +65,12 @@ public class PutRoleRequest extends ActionRequest implements Wri this.indicesPrivileges.addAll(Arrays.asList(privileges)); } - public void addIndex(String[] indices, String[] privileges, @Nullable String[] fields, @Nullable BytesReference query) { + public void addIndex(String[] indices, String[] privileges, FieldPermissions fieldPermissions, + @Nullable BytesReference query) { this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder() .indices(indices) .privileges(privileges) - .fields(fields) + .fieldPermissions(fieldPermissions) .query(query) .build()); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java index 410c6668613..db895d6a481 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequestBuilder.java @@ -11,6 +11,7 @@ import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Map; @@ -29,7 +30,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder privileges = new ArrayList<>(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - privileges.add(parseIndex(roleName, parser)); + privileges.add(parseIndex(roleName, parser, allow2xFormat)); } return privileges.toArray(new IndicesPrivileges[privileges.size()]); } - private static RoleDescriptor.IndicesPrivileges parseIndex(String roleName, XContentParser parser) throws IOException { + private static RoleDescriptor.IndicesPrivileges parseIndex(String roleName, XContentParser parser, + boolean allow2xFormat) throws IOException { XContentParser.Token token = parser.currentToken(); if (token != XContentParser.Token.START_OBJECT) { throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] value to " + @@ -238,7 +241,8 @@ public class RoleDescriptor implements ToXContent { String[] names = null; String query = null; String[] privileges = null; - String[] fields = null; + String[] grantedFields = null; + String[] deniedFields = null; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { currentFieldName = parser.currentName(); @@ -269,10 +273,58 @@ public class RoleDescriptor implements ToXContent { throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] " + "value to be null, a string, or an object, but found [{}] instead", roleName, currentFieldName, token); } + } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.FIELD_PERMISSIONS)) { + if (token == XContentParser.Token.START_OBJECT) { + token = parser.nextToken(); + do { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.GRANT_FIELDS)) { + parser.nextToken(); + grantedFields = readStringArray(roleName, parser, true); + if (grantedFields == null) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. {} must not " + + "be null.", roleName, Fields.GRANT_FIELDS); + } + } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.EXCEPT_FIELDS)) { + parser.nextToken(); + deniedFields = readStringArray(roleName, parser, true); + if (deniedFields == null) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. {} must not " + + "be null.", roleName, Fields.EXCEPT_FIELDS); + } + } else { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. " + + "\"{}\" only accepts options {} and {}, but got: {}", + roleName, Fields.FIELD_PERMISSIONS, Fields.GRANT_FIELDS, Fields.EXCEPT_FIELDS + , parser.currentName()); + } + } else { + if (token == XContentParser.Token.END_OBJECT) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. " + + "\"{}\" must not be empty.", roleName, Fields.FIELD_PERMISSIONS); + } else { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected {} but " + + "got {}.", roleName, XContentParser.Token.FIELD_NAME, + token); + } + } + } while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT); + } else { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected {} or {} but got {}" + + " in \"{}\".", roleName, XContentParser.Token.START_OBJECT, + XContentParser.Token.START_ARRAY, token, Fields.FIELD_PERMISSIONS); + } } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.PRIVILEGES)) { privileges = readStringArray(roleName, parser, true); - } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.FIELDS)) { - fields = readStringArray(roleName, parser, true); + } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.FIELD_PERMISSIONS_2X)) { + if (allow2xFormat) { + grantedFields = readStringArray(roleName, parser, true); + } else { + throw new ElasticsearchParseException("[\"fields\": [...]] format has changed for field" + + " permissions in role [{}], use [\"{}\": {\"{}\":[...]," + "\"{}\":[...]}] instead", + roleName, Fields.FIELD_PERMISSIONS, Fields.GRANT_FIELDS, Fields.EXCEPT_FIELDS, roleName); + } } else { throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. unexpected field [{}]", roleName, currentFieldName); @@ -286,17 +338,21 @@ public class RoleDescriptor implements ToXContent { throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. missing required [{}] field", roleName, Fields.PRIVILEGES.getPreferredName()); } + if (deniedFields != null && grantedFields == null) { + throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. {} requires {} if {} is given", + roleName, Fields.FIELD_PERMISSIONS, Fields.GRANT_FIELDS, Fields.EXCEPT_FIELDS); + } return RoleDescriptor.IndicesPrivileges.builder() .indices(names) .privileges(privileges) - .fields(fields) + .fieldPermissions(new FieldPermissions(grantedFields, deniedFields)) .query(query) .build(); } /** * A class representing permissions for a group of indices mapped to - * privileges, fields, and a query. + * privileges, field permissions, and a query. */ public static class IndicesPrivileges implements ToXContent, Streamable { @@ -304,7 +360,7 @@ public class RoleDescriptor implements ToXContent { private String[] indices; private String[] privileges; - private String[] fields; + private FieldPermissions fieldPermissions = new FieldPermissions(); private BytesReference query; private IndicesPrivileges() { @@ -322,9 +378,8 @@ public class RoleDescriptor implements ToXContent { return this.privileges; } - @Nullable - public String[] getFields() { - return this.fields; + public FieldPermissions getFieldPermissions() { + return fieldPermissions; } @Nullable @@ -337,9 +392,10 @@ public class RoleDescriptor implements ToXContent { StringBuilder sb = new StringBuilder("IndicesPrivileges["); sb.append("indices=[").append(Strings.arrayToCommaDelimitedString(indices)); sb.append("], privileges=[").append(Strings.arrayToCommaDelimitedString(privileges)); - sb.append("], fields=[").append(Strings.arrayToCommaDelimitedString(fields)); + sb.append("], "); + sb.append(fieldPermissions.toString()); if (query != null) { - sb.append("], query=").append(query.utf8ToString()); + sb.append(", query=").append(query.utf8ToString()); } sb.append("]"); return sb.toString(); @@ -354,7 +410,7 @@ public class RoleDescriptor implements ToXContent { if (!Arrays.equals(indices, that.indices)) return false; if (!Arrays.equals(privileges, that.privileges)) return false; - if (!Arrays.equals(fields, that.fields)) return false; + if (fieldPermissions.equals(that.fieldPermissions) == false) return false; return !(query != null ? !query.equals(that.query) : that.query != null); } @@ -362,7 +418,7 @@ public class RoleDescriptor implements ToXContent { public int hashCode() { int result = Arrays.hashCode(indices); result = 31 * result + Arrays.hashCode(privileges); - result = 31 * result + Arrays.hashCode(fields); + result = 31 * result + fieldPermissions.hashCode(); result = 31 * result + (query != null ? query.hashCode() : 0); return result; } @@ -372,9 +428,7 @@ public class RoleDescriptor implements ToXContent { builder.startObject(); builder.array("names", indices); builder.array("privileges", privileges); - if (fields != null) { - builder.array("fields", fields); - } + builder = fieldPermissions.toXContent(builder, params); if (query != null) { builder.field("query", query.utf8ToString()); } @@ -390,7 +444,7 @@ public class RoleDescriptor implements ToXContent { @Override public void readFrom(StreamInput in) throws IOException { this.indices = in.readStringArray(); - this.fields = in.readOptionalStringArray(); + this.fieldPermissions = new FieldPermissions(in); this.privileges = in.readStringArray(); this.query = in.readOptionalBytesReference(); } @@ -398,7 +452,7 @@ public class RoleDescriptor implements ToXContent { @Override public void writeTo(StreamOutput out) throws IOException { out.writeStringArray(indices); - out.writeOptionalStringArray(fields); + fieldPermissions.writeTo(out); out.writeStringArray(privileges); out.writeOptionalBytesReference(query); } @@ -420,8 +474,8 @@ public class RoleDescriptor implements ToXContent { return this; } - public Builder fields(@Nullable String... fields) { - indicesPrivileges.fields = fields; + public Builder fieldPermissions(FieldPermissions fieldPermissions) { + indicesPrivileges.fieldPermissions = fieldPermissions; return this; } @@ -453,7 +507,10 @@ public class RoleDescriptor implements ToXContent { ParseField NAMES = new ParseField("names"); ParseField QUERY = new ParseField("query"); ParseField PRIVILEGES = new ParseField("privileges"); - ParseField FIELDS = new ParseField("fields"); + ParseField FIELD_PERMISSIONS = new ParseField("field_security"); + ParseField FIELD_PERMISSIONS_2X = new ParseField("fields"); + ParseField GRANT_FIELDS = new ParseField("grant"); + ParseField EXCEPT_FIELDS = new ParseField("except"); ParseField METADATA = new ParseField("metadata"); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java index ef666500ca6..9a1a023ce98 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReader.java @@ -56,33 +56,30 @@ public final class FieldSubsetReader extends FilterLeafReader { * and so on. * @param in reader to filter * @param fieldNames fields to filter. - * @param negate {@code true} if this should be a negative set, meaning set of field names that is denied. */ - public static DirectoryReader wrap(DirectoryReader in, Set fieldNames, boolean negate) throws IOException { - return new FieldSubsetDirectoryReader(in, fieldNames, negate); + public static DirectoryReader wrap(DirectoryReader in, Set fieldNames) throws IOException { + return new FieldSubsetDirectoryReader(in, fieldNames); } // wraps subreaders with fieldsubsetreaders. static class FieldSubsetDirectoryReader extends FilterDirectoryReader { private final Set fieldNames; - private final boolean negate; - FieldSubsetDirectoryReader(DirectoryReader in, Set fieldNames, boolean negate) throws IOException { + FieldSubsetDirectoryReader(DirectoryReader in, final Set fieldNames) throws IOException { super(in, new FilterDirectoryReader.SubReaderWrapper() { @Override public LeafReader wrap(LeafReader reader) { - return new FieldSubsetReader(reader, fieldNames, negate); + return new FieldSubsetReader(reader, fieldNames); } }); this.fieldNames = fieldNames; - this.negate = negate; verifyNoOtherFieldSubsetDirectoryReaderIsWrapped(in); } @Override protected DirectoryReader doWrapDirectoryReader(DirectoryReader in) throws IOException { - return new FieldSubsetDirectoryReader(in, fieldNames, negate); + return new FieldSubsetDirectoryReader(in, fieldNames); } public Set getFieldNames() { @@ -114,23 +111,17 @@ public final class FieldSubsetReader extends FilterLeafReader { /** * Wrap a single segment, exposing a subset of its fields. - * @param fields set of field names that should be allowed - * @param negate {@code true} if this should be a negative set, meaning set of field names that is denied. */ - FieldSubsetReader(LeafReader in, Set fields, boolean negate) { + FieldSubsetReader(LeafReader in, Set fieldNames) { super(in); - // look at what fields the reader has, and preprocess a subset of them that are allowed ArrayList filteredInfos = new ArrayList<>(); for (FieldInfo fi : in.getFieldInfos()) { - if (fields.contains(fi.name) ^ negate) { + if (fieldNames.contains(fi.name)) { filteredInfos.add(fi); } } fieldInfos = new FieldInfos(filteredInfos.toArray(new FieldInfo[filteredInfos.size()])); - fieldNames = new String[filteredInfos.size()]; - for (int i = 0; i < fieldNames.length; i++) { - fieldNames[i] = filteredInfos.get(i).name; - } + this.fieldNames = fieldNames.toArray(new String[fieldNames.size()]); } /** returns true if this field is allowed. */ diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java index 0d2f500acd5..7fd832ab227 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authz.accesscontrol; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Collections; import java.util.HashSet; @@ -52,12 +53,12 @@ public class IndicesAccessControl { public static class IndexAccessControl { private final boolean granted; - private final Set fields; + private final FieldPermissions fieldPermissions; private final Set queries; - public IndexAccessControl(boolean granted, Set fields, Set queries) { + public IndexAccessControl(boolean granted, FieldPermissions fieldPermissions, Set queries) { this.granted = granted; - this.fields = fields; + this.fieldPermissions = fieldPermissions; this.queries = queries; } @@ -69,12 +70,10 @@ public class IndicesAccessControl { } /** - * @return The allowed fields for this index permissions. If null is returned then - * this means that there are no field level restrictions + * @return The allowed fields for this index permissions. */ - @Nullable - public Set getFields() { - return fields; + public FieldPermissions getFieldPermissions() { + return fieldPermissions; } /** @@ -99,21 +98,8 @@ public class IndicesAccessControl { return other; } - // this code is a bit of a pita, but right now we can't just initialize an empty set, - // because an empty Set means no permissions on fields and - // null means no field level security - // Also, if one grants no access to fields and the other grants all access, merging should result in all access... - Set fields = null; - if (this.fields != null && other.getFields() != null) { - fields = new HashSet<>(); - if (this.fields != null) { - fields.addAll(this.fields); - } - if (other.getFields() != null) { - fields.addAll(other.getFields()); - } - fields = unmodifiableSet(fields); - } + FieldPermissions newPermissions = FieldPermissions.merge(this.fieldPermissions, other.fieldPermissions); + Set queries = null; if (this.queries != null && other.getQueries() != null) { queries = new HashSet<>(); @@ -125,9 +111,9 @@ public class IndicesAccessControl { } queries = unmodifiableSet(queries); } - return new IndexAccessControl(granted, fields, queries); + return new IndexAccessControl(granted, newPermissions, queries); } - } + } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java index ad4f0d7823e..76d23cdbbdf 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCache.java @@ -67,7 +67,7 @@ public final class OptOutQueryCache extends AbstractIndexComponent implements Qu } IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(indexName); - if (indexAccessControl != null && indexAccessControl.getFields() != null) { + if (indexAccessControl != null && indexAccessControl.getFieldPermissions().hasFieldLevelSecurity()) { if (cachingIsSafe(weight, indexAccessControl)) { logger.trace("not opting out of the query cache. request for index [{}] is safe to cache", indexName); return indicesQueryCache.doCache(weight, policy); @@ -98,7 +98,7 @@ public final class OptOutQueryCache extends AbstractIndexComponent implements Qu // we successfully extracted the set of fields: check each one for (String field : fields) { // don't cache any internal fields (e.g. _field_names), these are complicated. - if (field.startsWith("_") || permissions.getFields().contains(field) == false) { + if (field.startsWith("_") || permissions.getFieldPermissions().grantsAccessTo(field) == false) { return false; } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java index 40418716552..e24eb6f8567 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapper.java @@ -170,15 +170,11 @@ public class SecurityIndexSearcherWrapper extends IndexSearcherWrapper { reader = DocumentSubsetReader.wrap(reader, bitsetFilterCache, new ConstantScoreQuery(filter.build())); } - if (permissions.getFields() != null) { + if (permissions.getFieldPermissions().hasFieldLevelSecurity()) { // now add the allowed fields based on the current granted permissions and : - Set allowedFields = new HashSet<>(allowedMetaFields); - for (String field : permissions.getFields()) { - allowedFields.addAll(mapperService.simpleMatchToIndexNames(field)); - } + Set allowedFields = permissions.getFieldPermissions().resolveAllowedFields(allowedMetaFields, mapperService); resolveParentChildJoinFields(allowedFields); - // TODO: support 'denied' fields (pass true as the 3rd parameter in this case) - reader = FieldSubsetReader.wrap(reader, allowedFields, false); + reader = FieldSubsetReader.wrap(reader, allowedFields); } return reader; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java new file mode 100644 index 00000000000..58bbb198f03 --- /dev/null +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java @@ -0,0 +1,286 @@ +/* + * 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.authz.permission; + +import dk.brics.automaton.Automaton; +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.regex.Regex; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.mapper.AllFieldMapper; +import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.support.Automatons; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Stores patterns to fields which access is granted or denied to and maintains an automaton that can be used to check if permission is + * allowed for a specific field. + * Field permissions are configured via a list of strings that are patterns a field has to match. Two lists determine whether or not a + * field is granted access to: + * 1. It has to match the patterns in grantedFieldsArray + * 2. it must not match the patterns in deniedFieldsArray + */ +public class FieldPermissions implements Writeable, ToXContent { + + // the patterns for fields which we allow access to. if gratedFieldsArray is null we assume that all fields are grated access to + String[] grantedFieldsArray; + // the patterns for fields which we deny access to. if this is an empty list or null we assume that we do not deny access to any + // field explicitly + String[] deniedFieldsArray; + // an automaton that matches all strings that match the patterns in permittedFieldsArray but does not match those that also match a + // pattern in deniedFieldsArray. If permittedFieldsAutomaton is null we assume that all fields are granted access to. + Automaton permittedFieldsAutomaton; + + // we cannot easily determine if all fields are allowed and we can therefore also allow access to the _all field hence we deny access + // to _all unless this was explicitly configured. + boolean allFieldIsAllowed = false; + + public FieldPermissions(StreamInput in) throws IOException { + this(in.readOptionalStringArray(), in.readOptionalStringArray()); + } + + public FieldPermissions(@Nullable String[] grantedFieldsArray, @Nullable String[] deniedFieldsArray) { + this.grantedFieldsArray = grantedFieldsArray; + this.deniedFieldsArray = deniedFieldsArray; + permittedFieldsAutomaton = initializePermittedFieldsAutomaton(grantedFieldsArray, deniedFieldsArray); + allFieldIsAllowed = checkAllFieldIsAllowed(grantedFieldsArray, deniedFieldsArray); + } + + private static boolean checkAllFieldIsAllowed(String[] grantedFieldsArray, String[] deniedFieldsArray) { + if (deniedFieldsArray != null) { + for (String fieldName : deniedFieldsArray) { + if (fieldName.equals(AllFieldMapper.NAME)) { + return false; + } + } + } + if (grantedFieldsArray != null) { + for (String fieldName : grantedFieldsArray) { + if (fieldName.equals(AllFieldMapper.NAME)) { + return true; + } + } + } + return false; + } + + private static Automaton initializePermittedFieldsAutomaton(final String[] grantedFieldsArray, + final String[] deniedFieldsArray) { + Automaton grantedFieldsAutomaton; + if (grantedFieldsArray == null || containsWildcard(grantedFieldsArray)) { + grantedFieldsAutomaton = Automatons.MATCH_ALL; + } else { + grantedFieldsAutomaton = Automatons.patterns(grantedFieldsArray); + } + Automaton deniedFieldsAutomaton; + if (deniedFieldsArray == null || deniedFieldsArray.length == 0) { + deniedFieldsAutomaton = Automatons.EMPTY; + } else { + deniedFieldsAutomaton = Automatons.patterns(deniedFieldsArray); + } + if (deniedFieldsAutomaton.subsetOf(grantedFieldsAutomaton) == false) { + throw new ElasticsearchSecurityException("Exceptions for field permissions must be a subset of the " + + "granted fields but " + Arrays.toString(deniedFieldsArray) + " is not a subset of " + + Arrays.toString(grantedFieldsArray)); + } + + grantedFieldsAutomaton = grantedFieldsAutomaton.minus(deniedFieldsAutomaton); + return grantedFieldsAutomaton; + } + + private static boolean containsWildcard(String[] grantedFieldsArray) { + for (String fieldPattern : grantedFieldsArray) { + if (Regex.isMatchAllPattern(fieldPattern)) { + return true; + } + } + return false; + } + + public FieldPermissions() { + this(null, null); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalStringArray(grantedFieldsArray); + out.writeOptionalStringArray(deniedFieldsArray); + } + + @Nullable + String[] getGrantedFieldsArray() { + return grantedFieldsArray; + } + + @Nullable + String[] getDeniedFieldsArray() { + return deniedFieldsArray; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (grantedFieldsArray != null || deniedFieldsArray != null) { + sb.append(RoleDescriptor.Fields.FIELD_PERMISSIONS).append("=["); + if (grantedFieldsArray == null) { + sb.append(RoleDescriptor.Fields.GRANT_FIELDS).append("=null"); + } else { + sb.append(RoleDescriptor.Fields.GRANT_FIELDS).append("=[") + .append(Strings.arrayToCommaDelimitedString(grantedFieldsArray)); + sb.append("]"); + } + if (deniedFieldsArray == null) { + sb.append(", ").append(RoleDescriptor.Fields.EXCEPT_FIELDS).append("=null"); + } else { + sb.append(", ").append(RoleDescriptor.Fields.EXCEPT_FIELDS).append("=[") + .append(Strings.arrayToCommaDelimitedString(deniedFieldsArray)); + sb.append("]"); + } + sb.append("]"); + } + return sb.toString(); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (grantedFieldsArray != null || deniedFieldsArray != null) { + builder.startObject(RoleDescriptor.Fields.FIELD_PERMISSIONS.getPreferredName()); + if (grantedFieldsArray != null) { + builder.array(RoleDescriptor.Fields.GRANT_FIELDS.getPreferredName(), grantedFieldsArray); + } + if (deniedFieldsArray != null) { + builder.array(RoleDescriptor.Fields.EXCEPT_FIELDS.getPreferredName(), deniedFieldsArray); + } + builder.endObject(); + } + return builder; + } + + /** + * Returns true if this field permission policy allows access to the field and false if not. + * fieldName can be a wildcard. + */ + public boolean grantsAccessTo(String fieldName) { + if (permittedFieldsAutomaton.isTotal()) { + return true; + } else { + return permittedFieldsAutomaton.run(fieldName); + } + } + + // Also, if one grants no access to fields and the other grants all access, merging should result in all access... + public static FieldPermissions merge(FieldPermissions p1, FieldPermissions p2) { + Automaton mergedPermittedFieldsAutomaton; + // we only allow the union of the two automatons + mergedPermittedFieldsAutomaton = p1.permittedFieldsAutomaton.union(p2.permittedFieldsAutomaton); + // need to minimize otherwise isTotal() might return false even if one of the merged ones returned true before + mergedPermittedFieldsAutomaton.minimize(); + // if one of them allows access to _all we allow it for the merged too + boolean allFieldIsAllowedInMerged = p1.allFieldIsAllowed || p2.allFieldIsAllowed; + return new MergedFieldPermissions(mergedPermittedFieldsAutomaton, allFieldIsAllowedInMerged); + } + + public boolean hasFieldLevelSecurity() { + return permittedFieldsAutomaton.isTotal() == false; + } + + public Set resolveAllowedFields(Set allowedMetaFields, MapperService mapperService) { + HashSet finalAllowedFields = new HashSet<>(); + // we always add the allowed meta fields because we must make sure access is not denied accidentally + finalAllowedFields.addAll(allowedMetaFields); + // now check all other fields if we allow them + Collection allFields = mapperService.simpleMatchToIndexNames("*"); + for (String fieldName : allFields) { + if (grantsAccessTo(fieldName)) { + finalAllowedFields.add(fieldName); + } + } + if (allFieldIsAllowed == false) { + // we probably added the _all field and now we have to remove it again + finalAllowedFields.remove("_all"); + } + return finalAllowedFields; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FieldPermissions that = (FieldPermissions) o; + + if (allFieldIsAllowed != that.allFieldIsAllowed) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(grantedFieldsArray, that.grantedFieldsArray)) return false; + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(deniedFieldsArray, that.deniedFieldsArray)) return false; + return permittedFieldsAutomaton.equals(that.permittedFieldsAutomaton); + + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(grantedFieldsArray); + result = 31 * result + Arrays.hashCode(deniedFieldsArray); + result = 31 * result + permittedFieldsAutomaton.hashCode(); + result = 31 * result + (allFieldIsAllowed ? 1 : 0); + return result; + } + + /** + * When we merge field permissions we need to union all the allowed fields. We do this by a union of the automatons + * that define which fields are granted access too. However, that means that after merging we cannot know anymore + * which strings defined the automatons. Hence we make a new class that only has an automaton for the fields that + * we grant access to and that throws an exception whenever we try to access the original patterns that lead to + * the automaton. + */ + public static class MergedFieldPermissions extends FieldPermissions { + public MergedFieldPermissions(Automaton grantedFields, boolean allFieldIsAllowed) { + assert grantedFields != null; + this.permittedFieldsAutomaton = grantedFields; + this.grantedFieldsArray = null; + this.deniedFieldsArray = null; + this.allFieldIsAllowed = allFieldIsAllowed; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + throw new UnsupportedOperationException("Cannot build xcontent for merged field permissions"); + } + + @Override + public String toString() { + throw new UnsupportedOperationException("Cannot build string for merged field permissions"); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + throw new UnsupportedOperationException("Cannot stream for merged field permissions"); + } + + @Nullable + public String[] getGrantedFieldsArray() { + throw new UnsupportedOperationException("Merged field permissions does not maintain sets"); + } + + @Nullable + public String[] getDeniedFieldsArray() { + throw new UnsupportedOperationException("Merged field permissions does not maintain sets"); + } + } +} diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java index 33f302bc104..0a29bc6f7b1 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.concurrent.ConcurrentHashMap; @@ -107,7 +108,7 @@ public interface IndicesPermission extends Permission, Iterable allAliasesAndIndices = metaData.getAliasAndIndexLookup(); - Map> rolesFieldsByIndex = new HashMap<>(); + Map> fieldPermissionsByIndex = new HashMap<>(); Map> roleQueriesByIndex = new HashMap<>(); Map grantedBuilder = new HashMap<>(); @@ -125,14 +126,10 @@ public interface IndicesPermission extends Permission, Iterable roleFields = rolesFieldsByIndex.get(index); - if (roleFields == null) { - roleFields = new HashSet<>(); - rolesFieldsByIndex.put(index, roleFields); - } - roleFields.addAll(group.getFields()); + if (fieldPermissionsByIndex.get(index) == null) { + fieldPermissionsByIndex.put(index, new HashSet<>()); } + fieldPermissionsByIndex.get(index).add(group.getFieldPermissions()); if (group.hasQuery()) { Set roleQueries = roleQueriesByIndex.get(index); if (roleQueries == null) { @@ -161,15 +158,17 @@ public interface IndicesPermission extends Permission, Iterable roleFields = rolesFieldsByIndex.get(index); - if (roleFields != null) { - if (roleFields.contains("*")) { - roleFields = null; - } else { - roleFields = unmodifiableSet(roleFields); + + FieldPermissions fieldPermissions = new FieldPermissions(); + Set indexFieldPermissions = fieldPermissionsByIndex.get(index); + if (indexFieldPermissions != null) { + // get the first field permission entry because we do not want the merge to overwrite granted fields with null + fieldPermissions = indexFieldPermissions.iterator().next(); + for (FieldPermissions fp : indexFieldPermissions) { + fieldPermissions = FieldPermissions.merge(fieldPermissions, fp); } } - indexPermissions.put(index, new IndicesAccessControl.IndexAccessControl(entry.getValue(), roleFields, roleQueries)); + indexPermissions.put(index, new IndicesAccessControl.IndexAccessControl(entry.getValue(), fieldPermissions, roleQueries)); } return unmodifiableMap(indexPermissions); } @@ -291,16 +290,21 @@ public interface IndicesPermission extends Permission, Iterable actionMatcher; private final String[] indices; private final Predicate indexNameMatcher; - private final List fields; + + public FieldPermissions getFieldPermissions() { + return fieldPermissions; + } + + private final FieldPermissions fieldPermissions; private final BytesReference query; - public Group(IndexPrivilege privilege, @Nullable List fields, @Nullable BytesReference query, String... indices) { + public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable BytesReference query, String... indices) { assert indices.length != 0; this.privilege = privilege; this.actionMatcher = privilege.predicate(); this.indices = indices; this.indexNameMatcher = new AutomatonPredicate(Automatons.patterns(indices)); - this.fields = fields; + this.fieldPermissions = Objects.requireNonNull(fieldPermissions); this.query = query; } @@ -312,11 +316,6 @@ public interface IndicesPermission extends Permission, Iterable getFields() { - return fields; - } - @Nullable public BytesReference getQuery() { return query; @@ -331,10 +330,6 @@ public interface IndicesPermission extends Permission, Iterable fields, BytesReference query, IndexPrivilege privilege, String... indices) { - groups.add(new IndicesPermission.Group(privilege, fields, query, indices)); + public Builder add(FieldPermissions fieldPermissions, BytesReference query, IndexPrivilege privilege, String... indices) { + groups.add(new IndicesPermission.Group(privilege, fieldPermissions, query, indices)); return this; } @@ -112,7 +110,7 @@ public class Role extends GlobalPermission { List list = new ArrayList<>(indicesPrivileges.length); for (RoleDescriptor.IndicesPrivileges privilege : indicesPrivileges) { list.add(new IndicesPermission.Group(IndexPrivilege.get(new Privilege.Name(privilege.getPrivileges())), - privilege.getFields() == null ? null : Arrays.asList(privilege.getFields()), + privilege.getFieldPermissions(), privilege.getQuery(), privilege.getIndices())); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java index daeb217b30b..136a40b35a0 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java @@ -100,7 +100,7 @@ public class FileRolesStore extends AbstractLifecycleComponent implements RolesS boolean fls = false; for (Role role : permissions.values()) { for (Group group : role.indices()) { - fls = fls || group.hasFields(); + fls = fls || group.getFieldPermissions().hasFieldLevelSecurity(); dls = dls || group.hasQuery(); } if (fls && dls) { @@ -205,7 +205,8 @@ public class FileRolesStore extends AbstractLifecycleComponent implements RolesS String roleName = descriptor.getName(); // first check if FLS/DLS is enabled on the role... for (RoleDescriptor.IndicesPrivileges privilege : descriptor.getIndicesPrivileges()) { - if ((privilege.getQuery() != null || privilege.getFields() != null) + + if ((privilege.getQuery() != null || privilege.getFieldPermissions().hasFieldLevelSecurity()) && XPackSettings.DLS_FLS_ENABLED.get(settings) == false) { logger.error("invalid role definition [{}] in roles file [{}]. document and field level security is not " + "enabled. set [{}] to [true] in the configuration file. skipping role...", roleName, path @@ -220,7 +221,7 @@ public class FileRolesStore extends AbstractLifecycleComponent implements RolesS } @Nullable - private static RoleDescriptor parseRoleDescriptor(String segment, Path path, Logger logger, + static RoleDescriptor parseRoleDescriptor(String segment, Path path, Logger logger, boolean resolvePermissions, Settings settings) { String roleName = null; try { @@ -243,7 +244,9 @@ public class FileRolesStore extends AbstractLifecycleComponent implements RolesS token = parser.nextToken(); if (token == XContentParser.Token.START_OBJECT) { - RoleDescriptor descriptor = RoleDescriptor.parse(roleName, parser); + // we pass true as last parameter because we do not want to reject files if field permissions + // are given in 2.x syntax + RoleDescriptor descriptor = RoleDescriptor.parse(roleName, parser, true); return descriptor; } else { logger.error("invalid role definition [{}] in roles file [{}]. skipping role...", roleName, path.toAbsolutePath()); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 49e9c64e314..7fce8667345 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authz.store; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; @@ -69,6 +70,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.index.query.QueryBuilders.existsQuery; import static org.elasticsearch.xpack.security.Security.setting; import static org.elasticsearch.xpack.security.SecurityTemplateService.securityIndexMappingAndTemplateUpToDate; @@ -207,7 +209,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C boolean hasHits = resp.getHits().getHits().length > 0; if (hasHits) { for (SearchHit hit : resp.getHits().getHits()) { - RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef()); + RoleDescriptor rd = transformRole(hit.getId(), hit.getSourceRef(), logger); if (rd != null) { roles.add(rd); } @@ -345,7 +347,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C for (RoleAndVersion rv : roleCache.values()) { Role role = rv.getRole(); for (Group group : role.indices()) { - fls = fls || group.hasFields(); + fls = fls || group.getFieldPermissions().hasFieldLevelSecurity(); dls = dls || group.hasQuery(); } if (fls && dls) { @@ -365,7 +367,11 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C if (fls == false) { builder.add(client.prepareSearch(SecurityTemplateService.SECURITY_INDEX_NAME) .setTypes(ROLE_DOC_TYPE) - .setQuery(QueryBuilders.existsQuery("indices.fields")) + .setQuery(QueryBuilders.boolQuery() + .should(existsQuery("indices.field_security.grant")) + .should(existsQuery("indices.field_security.except")) + // for backwardscompat with 2.x + .should(existsQuery("indices.fields"))) .setSize(0) .setTerminateAfter(1)); } @@ -373,7 +379,7 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C if (dls == false) { builder.add(client.prepareSearch(SecurityTemplateService.SECURITY_INDEX_NAME) .setTypes(ROLE_DOC_TYPE) - .setQuery(QueryBuilders.existsQuery("indices.query")) + .setQuery(existsQuery("indices.query")) .setSize(0) .setTerminateAfter(1)); } @@ -560,13 +566,15 @@ public class NativeRolesStore extends AbstractComponent implements RolesStore, C if (response.isExists() == false) { return null; } - return transformRole(response.getId(), response.getSourceAsBytesRef()); + return transformRole(response.getId(), response.getSourceAsBytesRef(), logger); } @Nullable - private RoleDescriptor transformRole(String name, BytesReference sourceBytes) { + static RoleDescriptor transformRole(String name, BytesReference sourceBytes, Logger logger) { try { - return RoleDescriptor.parse(name, sourceBytes); + // we pass true as last parameter because we do not want to reject permissions if the field permissions + // are given in 2.x syntax + return RoleDescriptor.parse(name, sourceBytes, true); } catch (Exception e) { logger.error((Supplier) () -> new ParameterizedMessage("error in the format of data for role [{}]", name), e); return null; diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java index 3b8f6e86c2a..93dc06e5d57 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java @@ -25,6 +25,7 @@ import static dk.brics.automaton.MinimizationOperations.minimize; public final class Automatons { public static final Automaton EMPTY = BasicAutomata.makeEmpty(); + public static final Automaton MATCH_ALL = BasicAutomata.makeAnyString(); static final char WILDCARD_STRING = '*'; // String equality with support for wildcards static final char WILDCARD_CHAR = '?'; // Char equality with support for wildcards diff --git a/elasticsearch/x-pack/security/src/main/resources/security-index-template.json b/elasticsearch/x-pack/security/src/main/resources/security-index-template.json index a9e72dd3877..d02cf0cb9ed 100644 --- a/elasticsearch/x-pack/security/src/main/resources/security-index-template.json +++ b/elasticsearch/x-pack/security/src/main/resources/security-index-template.json @@ -76,8 +76,15 @@ "indices" : { "type" : "object", "properties" : { - "fields" : { - "type" : "keyword" + "field_security" : { + "properties" : { + "grant": { + "type": "keyword" + }, + "except": { + "type": "keyword" + } + } }, "names" : { "type" : "keyword" diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityIT.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityIT.java index 7c5ad14f9f9..f8a6975113f 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityIT.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityIT.java @@ -8,6 +8,14 @@ package org.elasticsearch; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.xpack.XPackFeatureSet; +import org.elasticsearch.xpack.action.XPackUsageRequestBuilder; +import org.elasticsearch.xpack.action.XPackUsageResponse; +import org.elasticsearch.xpack.security.SecurityFeatureSet; +import org.elasticsearch.xpack.security.action.role.ClearRolesCacheRequestBuilder; +import org.elasticsearch.xpack.security.action.role.ClearRolesCacheResponse; import org.elasticsearch.xpack.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.security.action.role.PutRoleResponse; import org.elasticsearch.xpack.security.action.user.GetUsersResponse; @@ -15,16 +23,22 @@ import org.elasticsearch.xpack.security.action.user.PutUserResponse; import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.xpack.security.user.User; import java.util.Collections; +import java.util.List; import static java.util.Collections.singletonMap; +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordTokenTests.basicAuthHeaderValue; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; /** * Backwards compatibility test that loads some data from a pre-5.0 cluster and attempts to do some basic security stuff with it. It @@ -57,12 +71,30 @@ public class OldSecurityIndexBackwardsCompatibilityIT extends AbstractOldXPackIn } protected void checkVersion(Version version) throws Exception { - // test that user and roles are there - logger.info("Getting roles..."); + // wait for service to start SecurityClient securityClient = new SecurityClient(client()); assertBusy(() -> { assertEquals(NativeRolesStore.State.STARTED, internalCluster().getInstance(NativeRolesStore.class).state()); }); + + // make sure usage stats are still working even with old fls format + ClearRolesCacheResponse clearResponse = new ClearRolesCacheRequestBuilder(client()).get(); + assertThat(clearResponse.failures().size(), equalTo(0)); + XPackUsageResponse usageResponse = new XPackUsageRequestBuilder(client()).get(); + List usagesList = usageResponse.getUsages(); + for (XPackFeatureSet.Usage usage : usagesList) { + if (usage instanceof SecurityFeatureSet.Usage) { + XContentBuilder builder = jsonBuilder(); + usage.toXContent(builder, ToXContent.EMPTY_PARAMS); + assertThat(builder.string(), + anyOf(containsString("\"roles\":{\"native\":{\"size\":1,\"fls\":true,\"dls\":true}"), + containsString("\"roles\":{\"native\":{\"size\":1,\"dls\":true,\"fls\":true}"))); + + } + } + + // test that user and roles are there + logger.info("Getting roles..."); GetRolesResponse getRolesResponse = securityClient.prepareGetRoles("bwc_test_role").get(); assertThat(getRolesResponse.roles(), arrayWithSize(1)); RoleDescriptor role = getRolesResponse.roles()[0]; @@ -71,12 +103,19 @@ public class OldSecurityIndexBackwardsCompatibilityIT extends AbstractOldXPackIn RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; assertThat(indicesPrivileges.getIndices(), arrayWithSize(2)); assertArrayEquals(new String[] { "index1", "index2" }, indicesPrivileges.getIndices()); - assertArrayEquals(new String[] { "title", "body" }, indicesPrivileges.getFields()); + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("title")); + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("body")); assertArrayEquals(new String[] { "all" }, indicesPrivileges.getPrivileges()); assertEquals("{\"match\": {\"title\": \"foo\"}}", indicesPrivileges.getQuery().utf8ToString()); assertArrayEquals(new String[] { "all" }, role.getClusterPrivileges()); assertArrayEquals(new String[] { "other_user" }, role.getRunAs()); assertEquals("bwc_test_role", role.getName()); + // check x-content is rendered in new format although it comes from an old index + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + indicesPrivileges.getFieldPermissions().toXContent(builder, null); + builder.endObject(); + assertThat(builder.string(), equalTo("{\"field_security\":{\"grant\":[\"title\",\"body\"]}}")); logger.info("Getting users..."); assertBusy(() -> { @@ -110,7 +149,7 @@ public class OldSecurityIndexBackwardsCompatibilityIT extends AbstractOldXPackIn PutRoleResponse roleResponse = securityClient.preparePutRole("test_role").addIndices( new String[] { "index3" }, new String[] { "all" }, - new String[] { "title", "body" }, + new FieldPermissions(new String[]{"title", "body"}, null), new BytesArray("{\"term\": {\"title\":\"not\"}}")).cluster("all") .get(); assertTrue(roleResponse.isCreated()); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java index f13fa46719e..7dc924702f4 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.action.role.DeleteRoleResponse; import org.elasticsearch.xpack.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.security.action.role.PutRoleResponse; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; import org.elasticsearch.xpack.security.client.SecurityClient; import org.junit.Before; @@ -49,7 +50,7 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { for (String role : roles) { c.preparePutRole(role) .cluster("none") - .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null) + .addIndices(new String[] { "*" }, new String[] { "ALL" }, new FieldPermissions(), null) .get(); logger.debug("--> created role [{}]", role); } @@ -82,7 +83,7 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { for (String role : toModify) { PutRoleResponse response = securityClient.preparePutRole(role) .cluster("none") - .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null) + .addIndices(new String[] { "*" }, new String[] { "ALL" }, new FieldPermissions(), null) .runAs(role) .setRefreshPolicy(randomBoolean() ? IMMEDIATE : NONE) .get(); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java index 3ecfefd80d4..7e1cde66867 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/DocumentAndFieldLevelSecurityTests.java @@ -65,21 +65,24 @@ public class DocumentAndFieldLevelSecurityTests extends SecurityIntegTestCase { " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ field1 ]\n" + + " field_security:\n" + + " grant: [ field1 ]\n" + " query: '{\"term\" : {\"field1\" : \"value1\"}}'\n" + "role3:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ field2 ]\n" + + " field_security:\n" + + " grant: [ field2 ]\n" + " query: '{\"term\" : {\"field2\" : \"value2\"}}'\n" + "role4:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ field1 ]\n" + + " field_security:\n" + + " grant: [ field1 ]\n" + " query: '{\"term\" : {\"field2\" : \"value2\"}}'\n"; } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java index 1f74e5b8752..d93729f4044 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityRandomTests.java @@ -76,7 +76,7 @@ public class FieldLevelSecurityRandomTests extends SecurityIntegTestCase { StringBuilder roleFields = new StringBuilder(); for (String field : allowedFields) { - roleFields.append(" - ").append(field).append('\n'); + roleFields.append(" - ").append(field).append('\n'); } return super.configRoles() + @@ -90,7 +90,8 @@ public class FieldLevelSecurityRandomTests extends SecurityIntegTestCase { " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields:\n" +roleFields.toString() + + " field_security:\n" + + " grant:\n" + roleFields.toString() + "role3:\n" + " cluster:\n" + " - all\n" + @@ -98,22 +99,22 @@ public class FieldLevelSecurityRandomTests extends SecurityIntegTestCase { " - names: test\n" + " privileges:\n" + " - all\n" + - " fields:\n" + - " - field1\n" + + " field_security:\n" + + " grant: [ field1 ]\n" + "role4:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: test\n" + " privileges: [ ALL ]\n" + - " fields:\n" + - " - field2\n" + + " field_security:\n" + + " grant: [ field2 ]\n" + "role5:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: test\n" + " privileges: [ ALL ]\n" + - " fields:\n" + - " - field3\n"; + " field_security:\n" + + " grant: [ field3 ]\n"; } @Override diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java index 4a5a0c9f84c..42b74e8f875 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java @@ -90,27 +90,29 @@ public class FieldLevelSecurityTests extends SecurityIntegTestCase { " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ field1 ]\n" + + " field_security:\n" + + " grant: [ field1 ]\n" + "role3:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ field2, query* ]\n" + + " field_security:\n" + + " grant: [ field2, query* ]\n" + "role4:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields:\n" + - " - field1\n" + - " - field2\n" + + " field_security:\n" + + " grant: [ field1, field2]\n" + "role5:\n" + " cluster: [ all ]\n" + " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: []\n" + + " field_security:\n" + + " grant: [ ]\n" + "role6:\n" + " cluster: [ all ]\n" + " indices:\n" + @@ -121,7 +123,8 @@ public class FieldLevelSecurityTests extends SecurityIntegTestCase { " indices:\n" + " - names: '*'\n" + " privileges: [ ALL ]\n" + - " fields: [ 'field*' ]\n"; + " field_security:\n" + + " grant: [ 'field*' ]\n"; } @Override diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java index 22b1f189ae8..e9c6dfe43fa 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/integration/IndicesPermissionsWithAliasesWildcardsAndRegexsTests.java @@ -48,13 +48,16 @@ public class IndicesPermissionsWithAliasesWildcardsAndRegexsTests extends Securi " indices:\n" + " - names: 't*'\n" + " privileges: [ALL]\n" + - " fields: [ field1 ]\n" + + " field_security:\n" + + " grant: [ field1 ]\n" + " - names: 'my_alias'\n" + " privileges: [ALL]\n" + - " fields: [field2]\n" + + " field_security:\n" + + " grant: [ field2 ]\n" + " - names: '/an_.*/'\n" + " privileges: [ALL]\n" + - " fields: [field3]\n"; + " field_security:\n" + + " grant: [ field3 ]\n"; } @Override diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/role/PutRoleBuilderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/role/PutRoleBuilderTests.java new file mode 100644 index 00000000000..ca125eb8940 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/role/PutRoleBuilderTests.java @@ -0,0 +1,33 @@ +/* + * 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.role; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.client.NoOpClient; + +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.hamcrest.Matchers.containsString; + +public class PutRoleBuilderTests extends ESTestCase { + // test that we reject a role where field permissions are stored in 2.x format (fields:...) + public void testBWCFieldPermissions() throws Exception { + Path path = getDataPath("roles2xformat.json"); + byte[] bytes = Files.readAllBytes(path); + String roleString = new String(bytes, Charset.defaultCharset()); + try (Client client = new NoOpClient("testBWCFieldPermissions")) { + ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> new PutRoleRequestBuilder(client) + .source("role1", new BytesArray(roleString))); + assertThat(e.getDetailedMessage(), containsString("\"fields\": [...]] format has changed for field permissions in role " + + "[role1], use [\"field_security\": {\"grant\":[...],\"except\":[...]}] instead")); + } + } +} \ No newline at end of file diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 2d33333eb02..7281cfe19d6 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.SecurityTemplateService; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.xpack.security.transport.netty3.SecurityNetty3HttpServerTransport; import org.junit.BeforeClass; @@ -106,7 +107,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": {\"match_all\": {}}}")) .get(); addedRoles.add(rname); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java index 15a01289b88..fa95a68d60f 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java @@ -8,8 +8,9 @@ package org.elasticsearch.xpack.security.authc.esnative; import org.elasticsearch.cli.Command; import org.elasticsearch.cli.CommandTestCase; import org.elasticsearch.common.Strings; -import org.elasticsearch.xpack.security.authc.esnative.ESNativeRealmMigrateTool; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; + import static org.hamcrest.Matchers.equalTo; /** @@ -33,7 +34,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase { RoleDescriptor.IndicesPrivileges ip = RoleDescriptor.IndicesPrivileges.builder() .indices(new String[]{"i1", "i2", "i3"}) .privileges(new String[]{"all"}) - .fields(new String[]{"body"}) + .fieldPermissions(new FieldPermissions(new String[]{"body"}, null)) .build(); RoleDescriptor.IndicesPrivileges[] ips = new RoleDescriptor.IndicesPrivileges[1]; ips[0] = ip; @@ -42,8 +43,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\":[],\"metadata\":{}}")); - + "\"privileges\":[\"all\"],\"field_security\":{\"grant\":[\"body\"]}}],\"run_as\":[],\"metadata\":{}}")); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 5f142a40165..45540150e92 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.xpack.security.action.user.DeleteUserResponse; import org.elasticsearch.xpack.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.authz.permission.KibanaRole; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; @@ -150,7 +151,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + + "{\"match_all\": {}}}")) .metadata(metadata) .get(); logger.error("--> waiting for .security index"); @@ -167,13 +169,15 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + + "{\"match_all\": {}}}")) .get(); c.preparePutRole("test_role3") .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"query\": {\"match_all\": {}}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + + "{\"match_all\": {}}}")) .get(); logger.info("--> retrieving all roles"); @@ -200,7 +204,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role") .cluster("all") .addIndices(new String[] { "*" }, new String[] { "read" }, - new String[] { "body", "title" }, new BytesArray("{\"match_all\": {}}")) + new FieldPermissions(new String[] { "body", "title" }, null), + new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); @@ -294,7 +299,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role") .cluster("all") .addIndices(new String[]{"*"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); @@ -309,7 +314,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role") .cluster("none") .addIndices(new String[]{"*"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) .get(); try { client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster().prepareHealth().get(); @@ -326,7 +331,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role") .cluster("none") .addIndices(new String[]{"*"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) .get(); getRolesResponse = c.prepareGetRoles().names("test_role").get(); assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); @@ -342,7 +347,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role") .cluster("all") .addIndices(new String[]{"*"}, new String[]{"read"}, - new String[]{"body", "title"}, new BytesArray("{\"match_all\": {}}")) + new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) .get(); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); logger.error("--> waiting for .security index"); @@ -366,11 +371,11 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { // create some roles client.preparePutRole("admin_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, null, null) + .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), null) .get(); client.preparePutRole("read_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, null, null) + .addIndices(new String[]{"*"}, new String[]{"read"}, new FieldPermissions(), null) .get(); assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false)); @@ -453,11 +458,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { } else { client.preparePutRole("read_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, null, null) + .addIndices(new String[]{"*"}, new String[]{"read"}, new FieldPermissions(), null) .get(); } IndicesStatsResponse response = client().admin().indices().prepareStats("foo", SecurityTemplateService.SECURITY_INDEX_NAME).get(); + assertThat(response.getFailedShards(), is(0)); assertThat(response.getIndices().size(), is(2)); assertThat(response.getIndices().get(SecurityTemplateService.SECURITY_INDEX_NAME), notNullValue()); assertThat(response.getIndices().get(SecurityTemplateService.SECURITY_INDEX_NAME).getIndex(), @@ -570,14 +576,22 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { SecurityClient client = new SecurityClient(client()); PutRoleResponse putRoleResponse = client.preparePutRole("admin_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, null, null) + .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), null) .get(); assertThat(putRoleResponse.isCreated(), is(true)); roles++; if (fls) { - PutRoleResponse roleResponse = client.preparePutRole("admin_role_fls") + PutRoleResponse roleResponse; + String[] fields = new String[]{"foo"}; + FieldPermissions fieldPermissions; + if (randomBoolean()) { + fieldPermissions = new FieldPermissions(fields, null); + } else { + fieldPermissions = new FieldPermissions(null, fields); + } + roleResponse = client.preparePutRole("admin_role_fls") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, new String[] { "foo" }, null) + .addIndices(new String[]{"*"}, new String[]{"all"}, fieldPermissions, null) .get(); assertThat(roleResponse.isCreated(), is(true)); roles++; @@ -586,7 +600,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { if (dls) { PutRoleResponse roleResponse = client.preparePutRole("admin_role_dls") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, null, new BytesArray("{ \"match_all\": {} }")) + .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), new BytesArray("{ \"match_all\": {} }")) .get(); assertThat(roleResponse.isCreated(), is(true)); roles++; diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java index 94734cf77c4..ced68d2c634 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java @@ -15,6 +15,7 @@ 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 org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Map; @@ -38,13 +39,14 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fields("body", "title") + .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) .query("{\"query\": {\"match_all\": {}}}") .build() }; 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], metadata=[{}]]")); + "privileges=[read], field_security=[grant=[body,title], except=null], query={\"query\": {\"match_all\": {}}}],]" + + ", runAs=[sudo], metadata=[{}]]")); } public void testToXContent() throws Exception { @@ -52,37 +54,39 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fields("body", "title") + .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) .query("{\"query\": {\"match_all\": {}}}") .build() }; Map 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)); + RoleDescriptor parsed = RoleDescriptor.parse("test", builder.bytes(), false); + assertEquals(parsed, descriptor); } public void testParse() throws Exception { String q = "{\"cluster\":[\"a\", \"b\"]}"; - RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q)); + RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(0, rd.getIndicesPrivileges().length); assertArrayEquals(Strings.EMPTY_ARRAY, rd.getRunAs()); q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"]}"; - rd = RoleDescriptor.parse("test", new BytesArray(q)); + rd = RoleDescriptor.parse("test", new BytesArray(q), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); 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\", " + - "\"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"]}, {\"names\": \"idx2\", " + - "\"privileges\": [\"p3\"], \"fields\": [\"f1\", \"f2\"], \"query\": \"{\\\"match_all\\\": {}}\"}]}"; - rd = RoleDescriptor.parse("test", new BytesArray(q)); + "\"p2\"]}, {\"names\": \"idx2\", \"privileges\": [\"p3\"], \"field_security\": " + + "{\"grant\": [\"f1\", \"f2\"]}}, {\"names\": " + + "\"idx2\", " + + "\"privileges\": [\"p3\"], \"field_security\": {\"grant\": [\"f1\", \"f2\"]}, \"query\": \"{\\\"match_all\\\": {}}\"}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(3, rd.getIndicesPrivileges().length); @@ -90,7 +94,7 @@ public class RoleDescriptorTests extends ESTestCase { q = "{\"cluster\":[\"a\", \"b\"], \"run_as\": [\"m\", \"n\"], \"indices\": [{\"names\": [\"idx1\",\"idx2\"], \"privileges\": " + "[\"p1\", \"p2\"]}]}"; - rd = RoleDescriptor.parse("test", new BytesArray(q)); + rd = RoleDescriptor.parse("test", new BytesArray(q), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(1, rd.getIndicesPrivileges().length); @@ -99,7 +103,7 @@ public class RoleDescriptorTests extends ESTestCase { assertNull(rd.getIndicesPrivileges()[0].getQuery()); q = "{\"cluster\":[\"a\", \"b\"], \"metadata\":{\"foo\":\"bar\"}}"; - rd = RoleDescriptor.parse("test", new BytesArray(q)); + rd = RoleDescriptor.parse("test", new BytesArray(q), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(0, rd.getIndicesPrivileges().length); @@ -115,7 +119,7 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fields("body", "title") + .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) .query("{\"query\": {\"match_all\": {}}}") .build() }; @@ -131,7 +135,7 @@ public class RoleDescriptorTests extends ESTestCase { public void testParseEmptyQuery() 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)); + RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(json), false); assertEquals("test", rd.getName()); assertArrayEquals(new String[] { "a", "b" }, rd.getClusterPrivileges()); assertEquals(1, rd.getIndicesPrivileges().length); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java index e01d865a4c3..03c2003984e 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldDataCacheWithFieldSubsetReaderTests.java @@ -97,7 +97,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); global = sortedSetDVOrdinalsIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -110,7 +110,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), greaterThanOrEqualTo(1L)); } - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); for (LeafReaderContext context : ir.leaves()) { AtomicOrdinalsFieldData atomic = sortedSetDVOrdinalsIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -126,7 +126,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(numDocs)); assertThat(indexFieldDataCache.topLevelBuilds, equalTo(1)); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); global = pagedBytesIndexFieldData.loadGlobal(ir); atomic = global.load(ir.leaves().get(0)); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); @@ -141,7 +141,7 @@ public class FieldDataCacheWithFieldSubsetReaderTests extends ESTestCase { } assertThat(indexFieldDataCache.leafLevelBuilds, equalTo(ir.leaves().size())); - DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet(), false); + DirectoryReader ir = FieldSubsetReader.wrap(this.ir, Collections.emptySet()); for (LeafReaderContext context : ir.leaves()) { AtomicOrdinalsFieldData atomic = pagedBytesIndexFieldData.load(context); assertThat(atomic.getOrdinalsValues().getValueCount(), equalTo(0L)); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java index 0acbdf009de..5e0331edcfe 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/FieldSubsetReaderTests.java @@ -70,7 +70,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -102,7 +102,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -189,7 +189,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -216,7 +216,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -243,7 +243,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -270,7 +270,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -297,7 +297,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -324,7 +324,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -353,7 +353,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Fields vectors = ir.getTermVectors(0); @@ -383,7 +383,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -410,7 +410,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -442,7 +442,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -474,7 +474,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -506,7 +506,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -542,7 +542,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -577,7 +577,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -610,7 +610,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(SourceFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field Document d2 = ir.document(0); @@ -641,7 +641,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -690,7 +690,7 @@ public class FieldSubsetReaderTests extends ESTestCase { fields.add("fieldA"); fields.add("fieldC"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only two fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -738,7 +738,7 @@ public class FieldSubsetReaderTests extends ESTestCase { fields.add("fieldA"); fields.add("fieldC"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -774,7 +774,7 @@ public class FieldSubsetReaderTests extends ESTestCase { Set fields = new HashSet<>(); fields.add("fieldA"); fields.add(FieldNamesFieldMapper.NAME); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see only one field LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -803,7 +803,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("id"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); assertEquals(2, ir.numDocs()); assertEquals(1, ir.leaves().size()); @@ -838,7 +838,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldB"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // sees no fields assertNull(ir.getTermVectors(0)); @@ -858,7 +858,7 @@ public class FieldSubsetReaderTests extends ESTestCase { // open reader Set fields = Collections.singleton("fieldA"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, false); + DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields); // see no fields LeafReader segmentReader = ir.leaves().get(0).reader(); @@ -887,48 +887,12 @@ public class FieldSubsetReaderTests extends ESTestCase { IndexWriter iw = new IndexWriter(dir, iwc); iw.close(); - DirectoryReader directoryReader = DirectoryReader.open(dir); - directoryReader = FieldSubsetReader.wrap(directoryReader, Collections.emptySet(), false); - try { - FieldSubsetReader.wrap(directoryReader, Collections.emptySet(), false); - fail("shouldn't be able to wrap FieldSubsetDirectoryReader twice"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), equalTo("Can't wrap [class org.elasticsearch.xpack.security.authz.accesscontrol" + - ".FieldSubsetReader$FieldSubsetDirectoryReader] twice")); - } + final DirectoryReader directoryReader = FieldSubsetReader.wrap(DirectoryReader.open(dir), Collections.emptySet()); + IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> FieldSubsetReader.wrap(directoryReader, + Collections.emptySet())); + assertThat(e.getMessage(), equalTo("Can't wrap [class org.elasticsearch.xpack.security.authz.accesscontrol" + + ".FieldSubsetReader$FieldSubsetDirectoryReader] twice")); directoryReader.close(); dir.close(); } - - /** - * test filtering two string fields, with negated set - */ - public void testNegative() throws Exception { - Directory dir = newDirectory(); - IndexWriterConfig iwc = new IndexWriterConfig(null); - IndexWriter iw = new IndexWriter(dir, iwc); - - // add document with 2 fields - Document doc = new Document(); - doc.add(new StringField("fieldA", "test", Field.Store.NO)); - doc.add(new StringField("fieldB", "test", Field.Store.NO)); - iw.addDocument(doc); - - // open reader - Set fields = Collections.singleton("fieldB"); - DirectoryReader ir = FieldSubsetReader.wrap(DirectoryReader.open(iw), fields, true); - - // see only one field - LeafReader segmentReader = ir.leaves().get(0).reader(); - Set seenFields = new HashSet<>(); - for (String field : segmentReader.fields()) { - seenFields.add(field); - } - assertEquals(Collections.singleton("fieldA"), seenFields); - assertNotNull(segmentReader.terms("fieldA")); - assertNull(segmentReader.terms("fieldB")); - - TestUtil.checkReader(ir); - IOUtils.close(ir, iw, dir); - } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java index f5b89d07532..88964d2a0b7 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java @@ -5,15 +5,17 @@ */ package org.elasticsearch.xpack.security.authz.accesscontrol; +import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Collections; import java.util.Set; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -31,46 +33,52 @@ public class IndicesAccessControlTests extends ESTestCase { } public void testMergeFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, Sets.newHashSet("a", "c"), null); - IndexAccessControl other = new IndexAccessControl(true, Sets.newHashSet("b"), null); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{"a", "c"}, null), null); + IndexAccessControl other = new IndexAccessControl(true,new FieldPermissions(new String[]{"b"}, null), null); IndexAccessControl merge1 = indexAccessControl.merge(other); - assertThat(merge1.getFields(), containsInAnyOrder("a", "b", "c")); + assertTrue(merge1.getFieldPermissions().grantsAccessTo("a")); + assertTrue(merge1.getFieldPermissions().grantsAccessTo("b")); + assertTrue(merge1.getFieldPermissions().grantsAccessTo("c")); + assertTrue(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge1.isGranted(), is(true)); assertThat(merge1.getQueries(), nullValue()); IndexAccessControl merge2 = other.merge(indexAccessControl); - assertThat(merge2.getFields(), containsInAnyOrder("a", "b", "c")); + assertTrue(merge2.getFieldPermissions().grantsAccessTo("a")); + assertTrue(merge2.getFieldPermissions().grantsAccessTo("b")); + assertTrue(merge2.getFieldPermissions().grantsAccessTo("c")); + assertTrue(merge2.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge2.isGranted(), is(true)); assertThat(merge2.getQueries(), nullValue()); } public void testMergeEmptyAndNullFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, Collections.emptySet(), null); - IndexAccessControl other = new IndexAccessControl(true, null, null); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{}, null), null); + IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); IndexAccessControl merge1 = indexAccessControl.merge(other); - assertThat(merge1.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge1.isGranted(), is(true)); assertThat(merge1.getQueries(), nullValue()); IndexAccessControl merge2 = other.merge(indexAccessControl); - assertThat(merge2.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge2.isGranted(), is(true)); assertThat(merge2.getQueries(), nullValue()); } public void testMergeNullFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, Sets.newHashSet("a", "b"), null); - IndexAccessControl other = new IndexAccessControl(true, null, null); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{"a", "b"}, null), null); + IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); IndexAccessControl merge1 = indexAccessControl.merge(other); - assertThat(merge1.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge1.isGranted(), is(true)); assertThat(merge1.getQueries(), nullValue()); IndexAccessControl merge2 = other.merge(indexAccessControl); - assertThat(merge2.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge2.isGranted(), is(true)); assertThat(merge2.getQueries(), nullValue()); } @@ -78,81 +86,106 @@ public class IndicesAccessControlTests extends ESTestCase { public void testMergeQueries() { BytesReference query1 = new BytesArray(new byte[] { 0x1 }); BytesReference query2 = new BytesArray(new byte[] { 0x2 }); - IndexAccessControl indexAccessControl = new IndexAccessControl(true, null, Collections.singleton(query1)); - IndexAccessControl other = new IndexAccessControl(true, null, Collections.singleton(query2)); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton + (query1)); + IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton(query2)); IndexAccessControl merge1 = indexAccessControl.merge(other); - assertThat(merge1.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge1.isGranted(), is(true)); assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); IndexAccessControl merge2 = other.merge(indexAccessControl); - assertThat(merge2.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge2.isGranted(), is(true)); assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); } public void testMergeNullQuery() { BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - IndexAccessControl indexAccessControl = new IndexAccessControl(true, null, Collections.singleton(query1)); - IndexAccessControl other = new IndexAccessControl(true, null, null); + IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton + (query1)); + IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); IndexAccessControl merge1 = indexAccessControl.merge(other); - assertThat(merge1.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge1.isGranted(), is(true)); assertThat(merge1.getQueries(), nullValue()); IndexAccessControl merge2 = other.merge(indexAccessControl); - assertThat(merge2.getFields(), nullValue()); + assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); assertThat(merge2.isGranted(), is(true)); assertThat(merge1.getQueries(), nullValue()); } public void testMergeNotGrantedAndGranted() { - final Set notGrantedFields = randomFrom(Collections.emptySet(), Collections.singleton("baz"), null); + final String[] notGrantedFields = randomFrom(new String[]{}, new String[]{"baz"}, null); final Set notGrantedQueries = randomFrom(Collections.emptySet(), null, Collections.singleton(new BytesArray(new byte[] { randomByte() }))); - final IndexAccessControl indexAccessControl = new IndexAccessControl(false, notGrantedFields, notGrantedQueries); + final IndexAccessControl indexAccessControl = new IndexAccessControl(false, new FieldPermissions(notGrantedFields, null), + notGrantedQueries); final BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - final Set fields = - randomFrom(Collections.singleton("foo"), Sets.newHashSet("foo", "bar"), Collections.emptySet(), null); + final String[] fields = + randomFrom(new String[]{"foo"}, new String[]{"foo", "bar"}, new String[]{}, null); final Set queries = randomFrom(Collections.singleton(query1), Collections.emptySet(), null); - final IndexAccessControl other = new IndexAccessControl(true, fields, queries); + final IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(fields, null), queries); IndexAccessControl merged = indexAccessControl.merge(other); assertThat(merged.isGranted(), is(true)); - assertThat(merged.getFields(), equalTo(fields)); assertThat(merged.getQueries(), equalTo(queries)); - + if (fields == null) { + assertFalse(merged.getFieldPermissions().hasFieldLevelSecurity()); + } else { + assertTrue(merged.getFieldPermissions().hasFieldLevelSecurity()); + if (notGrantedFields != null) { + for (String field : notGrantedFields) { + assertFalse(merged.getFieldPermissions().grantsAccessTo(field)); + } + } + for (String field : fields) { + assertTrue(merged.getFieldPermissions().grantsAccessTo(field)); + } + } merged = other.merge(indexAccessControl); assertThat(merged.isGranted(), is(true)); - assertThat(merged.getFields(), equalTo(fields)); assertThat(merged.getQueries(), equalTo(queries)); + if (fields == null) { + assertFalse(merged.getFieldPermissions().hasFieldLevelSecurity()); + } else { + assertTrue(merged.getFieldPermissions().hasFieldLevelSecurity()); + if (notGrantedFields != null) { + for (String field : notGrantedFields) { + assertFalse(merged.getFieldPermissions().grantsAccessTo(field)); + } + } + for (String field : fields) { + assertTrue(merged.getFieldPermissions().grantsAccessTo(field)); + } + } } public void testMergeNotGranted() { - final Set notGrantedFields = randomFrom(Collections.emptySet(), Collections.singleton("baz"), null); + final String[] notGrantedFields = randomFrom(new String[]{}, new String[]{"baz"}, null); final Set notGrantedQueries = randomFrom(Collections.emptySet(), null, Collections.singleton(new BytesArray(new byte[] { randomByte() }))); - final IndexAccessControl indexAccessControl = new IndexAccessControl(false, notGrantedFields, notGrantedQueries); + final IndexAccessControl indexAccessControl = new IndexAccessControl(false, new FieldPermissions(notGrantedFields, null), + notGrantedQueries); final BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - final Set fields = - randomFrom(Collections.singleton("foo"), Sets.newHashSet("foo", "bar"), Collections.emptySet(), null); + final String[] fields = + randomFrom(new String[]{"foo"}, new String[]{"foo", "bar"}, new String[]{}, null); final Set queries = randomFrom(Collections.singleton(query1), Collections.emptySet(), null); - final IndexAccessControl other = new IndexAccessControl(false, fields, queries); + final IndexAccessControl other = new IndexAccessControl(false, new FieldPermissions(fields, null), queries); IndexAccessControl merged = indexAccessControl.merge(other); assertThat(merged.isGranted(), is(false)); - assertThat(merged.getFields(), equalTo(notGrantedFields)); assertThat(merged.getQueries(), equalTo(notGrantedQueries)); merged = other.merge(indexAccessControl); assertThat(merged.isGranted(), is(false)); - assertThat(merged.getFields(), equalTo(fields)); assertThat(merged.getQueries(), equalTo(queries)); } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java index 9c04e3f5d5c..272b70149c0 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java @@ -12,16 +12,19 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; +import org.elasticsearch.xpack.security.authz.permission.IndicesPermission; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; -import org.elasticsearch.test.ESTestCase; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.io.IOException; +import java.util.Map; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; @@ -41,49 +44,103 @@ public class IndicesPermissionTests extends ESTestCase { // basics: BytesReference query = new BytesArray("{}"); - List fields = Arrays.asList("_field"); - Role role = Role.builder("_role").add(fields, query, IndexPrivilege.ALL, "_index").build(); + String[] fields = new String[]{"_field"}; + Role role = Role.builder("_role").add(new FieldPermissions(fields, null), query, IndexPrivilege.ALL, "_index").build(); IndicesAccessControl permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); - assertThat(permissions.getIndexPermissions("_index").getFields().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getFields().iterator().next(), equalTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); // no document level security: - role = Role.builder("_role").add(fields, null, IndexPrivilege.ALL, "_index").build(); + role = Role.builder("_role").add(new FieldPermissions(fields, null), null, IndexPrivilege.ALL, "_index").build(); permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); - assertThat(permissions.getIndexPermissions("_index").getFields().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getFields().iterator().next(), equalTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries(), nullValue()); // no field level security: - role = Role.builder("_role").add(null, query, IndexPrivilege.ALL, "_index").build(); + role = Role.builder("_role").add(new FieldPermissions(), query, IndexPrivilege.ALL, "_index").build(); permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); - assertThat(permissions.getIndexPermissions("_index").getFields(), nullValue()); + assertFalse(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); // index group associated with an alias: - role = Role.builder("_role").add(fields, query, IndexPrivilege.ALL, "_alias").build(); + role = Role.builder("_role").add(new FieldPermissions(fields, null), query, IndexPrivilege.ALL, "_alias").build(); permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); - assertThat(permissions.getIndexPermissions("_index").getFields().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getFields().iterator().next(), equalTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); + assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); // match all fields - List allFields = randomFrom(Collections.singletonList("*"), Arrays.asList("foo", "*"), - Arrays.asList(randomAsciiOfLengthBetween(1, 10), "*")); - role = Role.builder("_role").add(allFields, query, IndexPrivilege.ALL, "_alias").build(); + String[] allFields = randomFrom(new String[]{"*"}, new String[]{"foo", "*"}, + new String[]{randomAsciiOfLengthBetween(1, 10), "*"}); + role = Role.builder("_role").add(new FieldPermissions(allFields, null), query, IndexPrivilege.ALL, "_alias").build(); permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); - assertThat(permissions.getIndexPermissions("_index").getFields(), nullValue()); + assertFalse(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); } + public void testIndicesPriviledgesStreaming() throws IOException { + BytesStreamOutput out = new BytesStreamOutput(); + String[] allowed = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + String[] denied = new String[]{allowed[0] + randomAsciiOfLength(5), allowed[1] + randomAsciiOfLength(5), + allowed[2] + randomAsciiOfLength(5)}; + FieldPermissions fieldPermissions = new FieldPermissions(allowed, denied); + RoleDescriptor.IndicesPrivileges.Builder indicesPrivileges = RoleDescriptor.IndicesPrivileges.builder(); + indicesPrivileges.fieldPermissions(fieldPermissions); + indicesPrivileges.query("{match_all:{}}"); + indicesPrivileges.indices(randomAsciiOfLength(5), randomAsciiOfLength(5), randomAsciiOfLength(5)); + indicesPrivileges.privileges("all", "read", "priv"); + indicesPrivileges.build().writeTo(out); + out.close(); + StreamInput in = out.bytes().streamInput(); + RoleDescriptor.IndicesPrivileges readIndicesPriviledges = RoleDescriptor.IndicesPrivileges.createFrom(in); + assertEquals(readIndicesPriviledges, indicesPrivileges.build()); + } + + // tests that field permissions are merged correctly when we authorize with several groups and don't crash when an index has no group + public void testCorePermissionAuthorize() { + final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); + final MetaData metaData = new MetaData.Builder() + .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .build(); + + IndicesPermission.Group group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1"); + IndicesPermission.Group group2 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(null, new + String[]{"denied_field"}), null, "a1"); + IndicesPermission.Core core = new IndicesPermission.Core(group1, group2); + Map authzMap = + core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), metaData); + assertTrue(authzMap.get("a1").getFieldPermissions().grantsAccessTo("denied_field")); + assertTrue(authzMap.get("a1").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5))); + // did not define anything for ba so we allow all + assertFalse(authzMap.get("ba").getFieldPermissions().hasFieldLevelSecurity()); + + // test with two indices + group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1"); + group2 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(null, new + String[]{"denied_field"}), null, "a1"); + IndicesPermission.Group group3 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(new String[]{"*_field"} + , new String[]{"denied_field"}), null, "a2"); + IndicesPermission.Group group4 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(new String[]{"*_field2"} + , new String[]{"denied_field2"}), null, "a2"); + core = new IndicesPermission.Core(group1, group2, group3, group4); + authzMap = core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "a2"), metaData); + assertFalse(authzMap.get("a1").getFieldPermissions().hasFieldLevelSecurity()); + assertFalse(authzMap.get("a2").getFieldPermissions().grantsAccessTo("denied_field2")); + assertFalse(authzMap.get("a2").getFieldPermissions().grantsAccessTo("denied_field")); + assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field")); + assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field2")); + assertTrue(authzMap.get("a2").getFieldPermissions().hasFieldLevelSecurity()); + } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCacheTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCacheTests.java new file mode 100644 index 00000000000..98aaaf9cbc2 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/OptOutQueryCacheTests.java @@ -0,0 +1,107 @@ +/* + * 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.authz.accesscontrol; + +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.Weight; +import org.apache.lucene.store.Directory; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; +import org.junit.After; +import org.junit.Before; + +import java.io.IOException; +import java.util.HashSet; + +/** Simple tests for opt out query cache*/ +public class OptOutQueryCacheTests extends ESTestCase { + IndexSearcher searcher; + Directory dir; + RandomIndexWriter w; + DirectoryReader reader; + + @Before + void initLuceneStuff() throws IOException { + dir = newDirectory(); + w = new RandomIndexWriter(random(), dir); + reader = w.getReader(); + searcher = newSearcher(reader); + } + + @After + void closeLuceneStuff() throws IOException { + w.close(); + dir.close(); + reader.close(); + } + public void testOptOutQueryCacheSafetyCheck() throws IOException { + + BooleanQuery.Builder builder = new BooleanQuery.Builder(); + builder.add(new TermQuery(new Term("foo", "bar")), BooleanClause.Occur.MUST); + builder.add(new TermQuery(new Term("no", "baz")), BooleanClause.Occur.MUST_NOT); + Weight weight = builder.build().createWeight(searcher, false); + + // whenever the allowed fields match the fields in the query and we do not deny access to any fields we allow caching. + IndicesAccessControl.IndexAccessControl permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"foo", "no"}, null), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"foo", "no"}, new String[]{}), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"*"}, new String[]{}), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"*"}, null), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"*"}, new String[]{"oof"}), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"f*", "n*"}, new String[]{}), new HashSet<>()); + assertTrue(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + // check we don't cache if a field is not allowed + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"foo"}, null), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"a*"}, new String[]{"aa"}), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(null, new String[]{"no"}), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(null, new String[]{"*"}), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{"foo", "no"}, new String[]{"no"}), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{}, new String[]{}), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + + permissions = new IndicesAccessControl.IndexAccessControl(true, + new FieldPermissions(new String[]{}, null), new HashSet<>()); + assertFalse(OptOutQueryCache.cachingIsSafe(weight, permissions)); + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java index aaa8ca57e74..fb80c2cefbf 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperIntegrationTests.java @@ -42,7 +42,7 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.script.ScriptService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; -import org.mockito.internal.matchers.Any; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Collections; import java.util.Optional; @@ -68,7 +68,8 @@ public class SecurityIndexSearcherWrapperIntegrationTests extends ESTestCase { .then(invocationOnMock -> Collections.singletonList((String) invocationOnMock.getArguments()[0])); ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, null, + IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, new + FieldPermissions(), singleton(new BytesArray("{\"match_all\" : {}}"))); IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(shardId.getIndex(), Settings.EMPTY); IndicesQueriesRegistry indicesQueriesRegistry = mock(IndicesQueriesRegistry.class); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java index 29c355e2a02..7e43f6ffe1a 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/SecurityIndexSearcherWrapperUnitTests.java @@ -8,8 +8,8 @@ package org.elasticsearch.xpack.security.authz.accesscontrol; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; -import org.apache.lucene.document.StringField; import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.StringField; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; @@ -78,6 +78,10 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.IndexSettingsModule; import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.IndexSettingsModule; +import org.elasticsearch.xpack.security.authz.accesscontrol.DocumentSubsetReader.DocumentSubsetDirectoryReader; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.junit.After; import org.junit.Before; import org.mockito.ArgumentCaptor; @@ -86,16 +90,16 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import static java.util.Collections.emptyList; -import static java.util.Collections.emptySet; -import static java.util.Collections.singleton; import static java.util.Collections.singletonMap; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xpack.security.authz.accesscontrol.SecurityIndexSearcherWrapper.intersectScorerAndRoleBits; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @@ -161,7 +165,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, - emptySet(), null); + new FieldPermissions(new String[]{}, null), null); return new IndicesAccessControl(true, singletonMap("_index", indexAccessControl)); } }; @@ -203,12 +207,12 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { public void testWildcards() throws Exception { XContentBuilder mappingSource = jsonBuilder().startObject().startObject("type").startObject("properties") - .startObject("field1_a").field("type", "text").endObject() - .startObject("field1_b").field("type", "text").endObject() - .startObject("field1_c").field("type", "text").endObject() - .startObject("field2_a").field("type", "text").endObject() - .startObject("field2_b").field("type", "text").endObject() - .startObject("field2_c").field("type", "text").endObject() + .startObject("field1_a").field("type", "text").endObject() + .startObject("field1_b").field("type", "text").endObject() + .startObject("field1_c").field("type", "text").endObject() + .startObject("field2_a").field("type", "text").endObject() + .startObject("field2_b").field("type", "text").endObject() + .startObject("field2_c").field("type", "text").endObject() .endObject().endObject().endObject(); mapperService.merge("type", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); @@ -219,25 +223,25 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { public void testDotNotion() throws Exception { XContentBuilder mappingSource = jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("foo") - .field("type", "object") - .startObject("properties") - .startObject("bar").field("type", "text").endObject() - .startObject("baz").field("type", "text").endObject() - .endObject() + .field("type", "object") + .startObject("properties") + .startObject("bar").field("type", "text").endObject() + .startObject("baz").field("type", "text").endObject() + .endObject() .endObject() .startObject("bar") - .field("type", "object") - .startObject("properties") - .startObject("foo").field("type", "text").endObject() - .startObject("baz").field("type", "text").endObject() - .endObject() + .field("type", "object") + .startObject("properties") + .startObject("foo").field("type", "text").endObject() + .startObject("baz").field("type", "text").endObject() + .endObject() .endObject() .startObject("baz") - .field("type", "object") - .startObject("properties") - .startObject("bar").field("type", "text").endObject() - .startObject("foo").field("type", "text").endObject() - .endObject() + .field("type", "object") + .startObject("properties") + .startObject("bar").field("type", "text").endObject() + .startObject("foo").field("type", "text").endObject() + .endObject() .endObject() .endObject().endObject().endObject(); mapperService.merge("type", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); @@ -251,25 +255,25 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { public void testParentChild() throws Exception { XContentBuilder mappingSource = jsonBuilder().startObject().startObject("parent1") .startObject("properties") - .startObject("field").field("type", "text").endObject() + .startObject("field").field("type", "text").endObject() .endObject() .endObject().endObject(); mapperService.merge("parent1", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); mappingSource = jsonBuilder().startObject().startObject("child1") .startObject("properties") - .startObject("field").field("type", "text").endObject() + .startObject("field").field("type", "text").endObject() .endObject() .startObject("_parent") - .field("type", "parent1") + .field("type", "parent1") .endObject() .endObject().endObject(); mapperService.merge("child1", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); mappingSource = jsonBuilder().startObject().startObject("child2") .startObject("properties") - .startObject("field").field("type", "text").endObject() + .startObject("field").field("type", "text").endObject() .endObject() .startObject("_parent") - .field("type", "parent1") + .field("type", "parent1") .endObject() .endObject().endObject(); mapperService.merge("child2", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); @@ -281,15 +285,15 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { mapperService.merge("parent2", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); mappingSource = jsonBuilder().startObject().startObject("child3") .startObject("properties") - .startObject("field").field("type", "text").endObject() + .startObject("field").field("type", "text").endObject() .endObject() .startObject("_parent") - .field("type", "parent2") + .field("type", "parent2") .endObject() .endObject().endObject(); mapperService.merge("child3", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); - assertResolvedFields("field1", "field1", ParentFieldMapper.joinField("parent1"), ParentFieldMapper.joinField("parent2")); + assertResolvedFields("field", "field", ParentFieldMapper.joinField("parent1"), ParentFieldMapper.joinField("parent2")); } public void testDelegateSimilarity() throws Exception { @@ -393,10 +397,142 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { directory.close(); } + public void testFieldPermissionsWithFieldExceptions() throws Exception { + XContentBuilder mappingSource = jsonBuilder().startObject().startObject("some_type") + .startObject("properties") + .startObject("field1").field("type", "text").endObject() + .startObject("field2").field("type", "text").endObject() + .startObject("xfield3").field("type", "text").endObject() + .endObject() + .endObject().endObject(); + mapperService.merge("some_type", new CompressedXContent(mappingSource.string()), MapperService.MergeReason.MAPPING_UPDATE, false); + securityIndexSearcherWrapper = + new SecurityIndexSearcherWrapper(indexSettings, null, mapperService, null, threadContext, licenseState, null); + Set allowedMetaFields = securityIndexSearcherWrapper.getAllowedMetaFields(); + String[] grantedFields = new String[]{}; + String[] deniedFields; + // Presence of fields in a role with an empty array implies access to no fields except the meta fields + Set resolvedAllowedFields = new FieldPermissions(grantedFields, randomBoolean() ? null : new String[]{}) + .resolveAllowedFields(allowedMetaFields, mapperService); + Set expectedResultSet = new HashSet<>(allowedMetaFields); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // make sure meta fields cannot be denied access to + deniedFields = allowedMetaFields.toArray(new String[allowedMetaFields.size()]); + resolvedAllowedFields = new FieldPermissions(null, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1", "field2", "xfield3")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // check we can add all fields with * + grantedFields = new String[]{"*"}; + resolvedAllowedFields = new FieldPermissions(grantedFields, randomBoolean() ? null : new String[]{}) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1", "field2", "xfield3")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // same with null + resolvedAllowedFields = new FieldPermissions(grantedFields, randomBoolean() ? null : new String[]{}) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1", "field2", "xfield3")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // check we remove only excluded fields + grantedFields = new String[]{"*"}; + deniedFields = new String[]{"xfield3"}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1", "field2")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // same with null + deniedFields = new String[]{"field1"}; + resolvedAllowedFields = new FieldPermissions(null, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field2", "xfield3")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // some other checks + grantedFields = new String[]{"field*"}; + deniedFields = new String[]{"field1", "field2"}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + grantedFields = new String[]{"field1", "field2"}; + deniedFields = new String[]{"field2"}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + grantedFields = new String[]{"field*"}; + deniedFields = new String[]{"field2"}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + deniedFields = new String[]{"field*"}; + resolvedAllowedFields = new FieldPermissions(null, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("xfield3")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // empty array for allowed fields always means no field is allowed + grantedFields = new String[]{}; + deniedFields = new String[]{}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // make sure all field can be explicitly allowed + grantedFields = new String[]{"_all", "*"}; + deniedFields = randomBoolean() ? null : new String[]{}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("field1", "field2", "xfield3", "_all")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + + // make sure all field can be explicitly allowed + grantedFields = new String[]{"_all"}; + deniedFields = randomBoolean() ? null : new String[]{}; + resolvedAllowedFields = new FieldPermissions(grantedFields, deniedFields) + .resolveAllowedFields(allowedMetaFields, mapperService); + expectedResultSet = new HashSet<>(allowedMetaFields); + expectedResultSet.addAll(Arrays.asList("_all")); + assertThat(resolvedAllowedFields.size(), equalTo(expectedResultSet.size())); + assertThat(resolvedAllowedFields, containsInAnyOrder(expectedResultSet.toArray())); + } + private SparseFixedBitSet query(LeafReaderContext leaf, String field, String value) throws IOException { SparseFixedBitSet sparseFixedBitSet = new SparseFixedBitSet(leaf.reader().maxDoc()); TermsEnum tenum = leaf.reader().terms(field).iterator(); - while (tenum.next().utf8ToString().equals(value) == false) {} + while (tenum.next().utf8ToString().equals(value) == false) { + } PostingsEnum penum = tenum.postings(null); sparseFixedBitSet.or(penum); return sparseFixedBitSet; @@ -408,7 +544,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { @Override protected IndicesAccessControl getIndicesAccessControl() { IndicesAccessControl.IndexAccessControl indexAccessControl = new IndicesAccessControl.IndexAccessControl(true, - singleton(expression), null); + new FieldPermissions(new String[]{expression}, null), null); return new IndicesAccessControl(true, singletonMap("_index", indexAccessControl)); } }; @@ -496,7 +632,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { private final Weight weight; private final Set seenLeaves = Collections.newSetFromMap(new IdentityHashMap<>()); - + protected CreateScorerOnceWeight(Weight weight) { super(weight.getQuery()); this.weight = weight; @@ -608,6 +744,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase { public void onCache(ShardId shardId, Accountable accountable) { } + @Override public void onRemoval(ShardId shardId, Accountable accountable) { diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java new file mode 100644 index 00000000000..15729d70905 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java @@ -0,0 +1,241 @@ +/* + * 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.authz.permission; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; + +import java.io.IOException; + +import static org.hamcrest.Matchers.containsString; + +public class FieldPermissionTests extends ESTestCase { + + public void testParseFieldPermissions() throws Exception { + String q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": [\"f1\", \"f2\", \"f3\", \"f4\"]," + + "\"except\": [\"f3\",\"f4\"]" + + "}}]}"; + RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), + new String[]{"f1", "f2", "f3", "f4"}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f3", "f4"}); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"except\": [\"f3\",\"f4\"]," + + "\"grant\": [\"f1\", \"f2\", \"f3\", \"f4\"]" + + "}}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), + new String[]{"f1", "f2", "f3", "f4"}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f3", "f4"}); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": [\"f1\", \"f2\"]" + + "}}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{"f1", "f2"}); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": []" + + "}}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"except\": []," + + "\"grant\": []" + + "}}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{}); + + final String exceptWithoutGrant = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"except\": [\"f1\"]" + + "}}]}"; + ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (exceptWithoutGrant), false)); + assertThat(e.getDetailedMessage(), containsString("failed to parse indices privileges for role [test]. field_security requires " + + "grant if except is given")); + + final String grantNull = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": null" + + "}}]}"; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (grantNull), false)); + assertThat(e.getDetailedMessage(), containsString("failed to parse indices privileges for role [test]. grant must not be null.")); + + final String exceptNull = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": [\"*\"]," + + "\"except\": null" + + "}}]}"; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (exceptNull), false)); + assertThat(e.getDetailedMessage(), containsString("failed to parse indices privileges for role [test]. except must not be null.")); + + final String exceptGrantNull = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": null," + + "\"except\": null" + + "}}]}"; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (exceptGrantNull), false)); + assertThat(e.getDetailedMessage(), containsString("failed to parse indices privileges for role [test]. grant must not be null.")); + + final String bothFieldsMissing = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "}}]}"; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (bothFieldsMissing), false)); + assertThat(e.getDetailedMessage(), containsString("failed to parse indices privileges for role [test]. \"field_security\" " + + "must not be empty.")); + + // try with two indices and mix order a little + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"field_security\": {" + + "\"grant\": []" + + "}}," + + "{\"names\": \"idx3\",\n" + + " \"field_security\": {\n" + + " \"grant\": [\"*\"], \n" + + " \"except\": [\"f2\"]}," + + "\"privileges\": [\"p3\"]}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), false); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertArrayEquals(rd.getIndicesPrivileges()[1].getFieldPermissions().getGrantedFieldsArray(), new String[]{"*"}); + assertArrayEquals(rd.getIndicesPrivileges()[1].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f2"}); + } + + // test old syntax for field permissions + public void testBWCFieldPermissions() throws Exception { + String q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"fields\": [\"f1\", \"f2\"]" + + "}]}"; + RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q), true); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{"f1", "f2"}); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + + final String failingQuery = q; + ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (failingQuery), false)); + assertThat(e.getDetailedMessage(), containsString("[\"fields\": [...]] format has changed for field permissions in role [test]" + + ", use [\"field_security\": {\"grant\":[...],\"except\":[...]}] instead")); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"fields\": []" + + "}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), true); + assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + final String failingQuery2 = q; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray + (failingQuery2), false)); + assertThat(e.getDetailedMessage(), containsString("[\"fields\": [...]] format has changed for field permissions in role [test]" + + ", use [\"field_security\": {\"grant\":[...],\"except\":[...]}] instead")); + + q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + + "\"fields\": null" + + "}]}"; + rd = RoleDescriptor.parse("test", new BytesArray(q), true); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray()); + assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + final String failingQuery3 = q; + e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray(failingQuery3), false)); + assertThat(e.getDetailedMessage(), containsString("[\"fields\": [...]] format has changed for field permissions in role [test]" + + ", use [\"field_security\": {\"grant\":[...],\"except\":[...]}] instead")); + } + + public void testMergeFieldPermissions() { + String allowedPrefix1 = randomAsciiOfLength(5); + String allowedPrefix2 = randomAsciiOfLength(5); + String[] allowed1 = new String[]{allowedPrefix1 + "*"}; + String[] allowed2 = new String[]{allowedPrefix2 + "*"}; + String[] denied1 = new String[]{allowedPrefix1 + "a"}; + String[] denied2 = new String[]{allowedPrefix2 + "a"}; + FieldPermissions fieldPermissions1 = new FieldPermissions(allowed1, denied1); + FieldPermissions fieldPermissions2 = new FieldPermissions(allowed2, denied2); + FieldPermissions mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); + assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix1 + "b")); + assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix2 + "b")); + assertFalse(mergedFieldPermissions.grantsAccessTo(denied1[0])); + assertFalse(mergedFieldPermissions.grantsAccessTo(denied2[0])); + + allowed1 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + allowed2 = null; + denied1 = new String[]{allowed1[0] + "a", allowed1[1] + "a"}; + denied2 = null; + fieldPermissions1 = new FieldPermissions(allowed1, denied1); + fieldPermissions2 = new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); + assertFalse(mergedFieldPermissions.hasFieldLevelSecurity()); + + allowed1 = new String[]{}; + allowed2 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + denied1 = new String[]{}; + denied2 = new String[]{allowed2[0] + "a", allowed2[1] + "a"}; + fieldPermissions1 = new FieldPermissions(allowed1, denied1); + fieldPermissions2 = new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); + for (String field : allowed2) { + assertTrue(mergedFieldPermissions.grantsAccessTo(field)); + } + for (String field : denied2) { + assertFalse(mergedFieldPermissions.grantsAccessTo(field)); + } + + allowed1 = randomBoolean() ? null : new String[]{"*"}; + allowed2 = randomBoolean() ? null : new String[]{"*"}; + denied1 = new String[]{"a"}; + denied2 = new String[]{"b"}; + fieldPermissions1 = new FieldPermissions(allowed1, denied1); + fieldPermissions2 = new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); + assertTrue(mergedFieldPermissions.grantsAccessTo("a")); + assertTrue(mergedFieldPermissions.grantsAccessTo("b")); + + // test merge does not remove _all + allowed1 = new String[]{"_all"}; + allowed2 = new String[]{}; + denied1 = null; + denied2 = null; + fieldPermissions1 = new FieldPermissions(allowed1, denied1); + assertTrue(fieldPermissions1.allFieldIsAllowed); + fieldPermissions2 = new FieldPermissions(allowed2, denied2); + assertFalse(fieldPermissions2.allFieldIsAllowed); + mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); + assertTrue(mergedFieldPermissions.grantsAccessTo("_all")); + assertTrue(mergedFieldPermissions.allFieldIsAllowed); + } + + public void testFieldPermissionsStreaming() throws IOException { + BytesStreamOutput out = new BytesStreamOutput(); + String[] allowed = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + String[] denied = new String[]{allowed[0] + randomAsciiOfLength(5), allowed[1] + randomAsciiOfLength(5), + allowed[2] + randomAsciiOfLength(5)}; + FieldPermissions fieldPermissions = new FieldPermissions(allowed, denied); + out.writeOptionalWriteable(fieldPermissions); + out.close(); + StreamInput in = out.bytes().streamInput(); + FieldPermissions readFieldPermissions = in.readOptionalWriteable(FieldPermissions::new); + // order should be preserved in any case + assertEquals(readFieldPermissions, fieldPermissions); + } +} diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java index 45f56ed63c5..02512db9e33 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java @@ -9,23 +9,26 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.audit.logfile.CapturingLogger; import org.elasticsearch.xpack.security.authc.support.RefreshListener; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.security.authz.permission.IndicesPermission; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.permission.RunAsPermission; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.threadpool.TestThreadPool; -import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.watcher.ResourceWatcherService; import java.io.BufferedWriter; +import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -37,7 +40,6 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; @@ -163,7 +165,9 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices()[0], equalTo("field_idx")); assertThat(group.privilege(), notNullValue()); assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); - assertThat(group.getFields(), contains("foo", "boo")); + assertTrue(group.getFieldPermissions().grantsAccessTo("foo")); + assertTrue(group.getFieldPermissions().grantsAccessTo("boo")); + assertTrue(group.getFieldPermissions().hasFieldLevelSecurity()); role = roles.get("role_query"); assertThat(role, notNullValue()); @@ -181,7 +185,7 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices()[0], equalTo("query_idx")); assertThat(group.privilege(), notNullValue()); assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); - assertThat(group.getFields(), nullValue()); + assertFalse(group.getFieldPermissions().hasFieldLevelSecurity()); assertThat(group.getQuery(), notNullValue()); role = roles.get("role_query_fields"); @@ -200,7 +204,9 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices()[0], equalTo("query_fields_idx")); assertThat(group.privilege(), notNullValue()); assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); - assertThat(group.getFields(), contains("foo", "boo")); + assertTrue(group.getFieldPermissions().grantsAccessTo("foo")); + assertTrue(group.getFieldPermissions().grantsAccessTo("boo")); + assertTrue(group.getFieldPermissions().hasFieldLevelSecurity()); assertThat(group.getQuery(), notNullValue()); } @@ -395,4 +401,16 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(usageStats.get("fls"), is(flsDlsEnabled)); assertThat(usageStats.get("dls"), is(flsDlsEnabled)); } + + // test that we can read a role where field permissions are stored in 2.x format (fields:...) + public void testBWCFieldPermissions() throws IOException { + Path path = getDataPath("roles2xformat.yml"); + byte[] bytes = Files.readAllBytes(path); + String roleString = new String(bytes, Charset.defaultCharset()); + RoleDescriptor role = FileRolesStore.parseRoleDescriptor(roleString, path, logger, true, + Settings.EMPTY); + RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("foo")); + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("boo")); + } } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java new file mode 100644 index 00000000000..b5590627359 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java @@ -0,0 +1,29 @@ +/* + * 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.authz.store; + +import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +public class NativeRolesStoreTests extends ESTestCase { + + // test that we can read a role where field permissions are stored in 2.x format (fields:...) + public void testBWCFieldPermissions() throws IOException { + Path path = getDataPath("roles2xformat.json"); + byte[] bytes = Files.readAllBytes(path); + String roleString = new String(bytes, Charset.defaultCharset()); + RoleDescriptor role = NativeRolesStore.transformRole("role1", new BytesArray(roleString), logger); + RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("foo")); + assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("boo")); + } +} diff --git a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/action/role/roles2xformat.json b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/action/role/roles2xformat.json new file mode 100644 index 00000000000..0e356ba10d5 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/action/role/roles2xformat.json @@ -0,0 +1,19 @@ +{ + "indices": [ + { + "names": [ + "test" + ], + "privileges": [ + "READ" + ], + "query": { + "match_all": {} + }, + "fields": [ + "foo", + "boo" + ] + } + ] +} diff --git a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles.yml b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles.yml index 47eda4b6dae..99459c5f5ec 100644 --- a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles.yml +++ b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles.yml @@ -45,9 +45,10 @@ role_fields: - 'field_idx' privileges: - READ - fields: - - foo - - boo + field_security: + grant: + - foo + - boo role_query: indices: @@ -65,6 +66,7 @@ role_query_fields: - READ query: match_all: - fields: - - foo - - boo \ No newline at end of file + field_security: + grant: + - foo + - boo \ No newline at end of file diff --git a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.json b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.json new file mode 100644 index 00000000000..0e356ba10d5 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.json @@ -0,0 +1,19 @@ +{ + "indices": [ + { + "names": [ + "test" + ], + "privileges": [ + "READ" + ], + "query": { + "match_all": {} + }, + "fields": [ + "foo", + "boo" + ] + } + ] +} diff --git a/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.yml b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.yml new file mode 100644 index 00000000000..ebfdce617a0 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/resources/org/elasticsearch/xpack/security/authz/store/roles2xformat.yml @@ -0,0 +1,11 @@ +role1: + indices: + - names: + - 'test' + privileges: + - READ + query: + match_all: + fields: + - foo + - boo \ No newline at end of file