diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermission.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermission.java index 0cd4e8a8b0ddc..da6af4ec7cbdc 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermission.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermission.java @@ -104,15 +104,18 @@ public ResourcePrivilegesMap checkResourcePrivileges(final String applicationNam for (String checkResource : checkForResources) { for (String checkPrivilegeName : checkForPrivilegeNames) { final Set nameSet = Collections.singleton(checkPrivilegeName); - final ApplicationPrivilege checkPrivilege = ApplicationPrivilege.get(applicationName, nameSet, storedPrivileges); - assert checkPrivilege.getApplication().equals(applicationName) : "Privilege " + checkPrivilege + " should have application " - + applicationName; - assert checkPrivilege.name().equals(nameSet) : "Privilege " + checkPrivilege + " should have name " + nameSet; - - if (grants(checkPrivilege, checkResource)) { - resourcePrivilegesMapBuilder.addResourcePrivilege(checkResource, checkPrivilegeName, Boolean.TRUE); - } else { - resourcePrivilegesMapBuilder.addResourcePrivilege(checkResource, checkPrivilegeName, Boolean.FALSE); + final Set checkPrivileges = ApplicationPrivilege.get(applicationName, nameSet, storedPrivileges); + logger.trace("Resolved privileges [{}] for [{},{}]", checkPrivileges, applicationName, nameSet); + for (ApplicationPrivilege checkPrivilege : checkPrivileges) { + assert Automatons.predicate(applicationName).test(checkPrivilege.getApplication()) : "Privilege " + checkPrivilege + + " should have application " + applicationName; + assert checkPrivilege.name().equals(nameSet) : "Privilege " + checkPrivilege + " should have name " + nameSet; + + if (grants(checkPrivilege, checkResource)) { + resourcePrivilegesMapBuilder.addResourcePrivilege(checkResource, checkPrivilegeName, Boolean.TRUE); + } else { + resourcePrivilegesMapBuilder.addResourcePrivilege(checkResource, checkPrivilegeName, Boolean.FALSE); + } } } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilege.java index 13db17a63bb0d..c4460b000e6d8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilege.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.authz.privilege; import org.elasticsearch.common.Strings; +import org.elasticsearch.xpack.core.security.support.Automatons; import java.util.Arrays; import java.util.Collection; @@ -15,6 +16,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -101,7 +103,7 @@ private static void validateApplicationName(String application, boolean allowWil if (allowWildcard == false) { throw new IllegalArgumentException("Application names may not contain '*' (found '" + application + "')"); } - if(application.equals("*")) { + if (application.equals("*")) { // this is allowed and short-circuiting here makes the later validation simpler return; } @@ -128,7 +130,10 @@ private static void validateApplicationName(String application, boolean allowWil } if (parts.length > 1) { - final String suffix = parts[1]; + String suffix = parts[1]; + if (allowWildcard && suffix.endsWith("*")) { + suffix = suffix.substring(0, suffix.length() - 1); + } if (Strings.validFileName(suffix) == false) { throw new IllegalArgumentException("An application name suffix may not contain any of the characters '" + Strings.collectionToDelimitedString(Strings.INVALID_FILENAME_CHARS, "") + "' (found '" + suffix + "')"); @@ -165,20 +170,38 @@ public static void validatePrivilegeOrActionName(String name) { } /** - * Finds or creates an application privileges with the provided names. + * Finds or creates a collection of application privileges with the provided names. + * If application is a wildcard, it will be expanded to all matching application names in {@code stored} * Each element in {@code name} may be the name of a stored privilege (to be resolved from {@code stored}, or a bespoke action pattern. */ - public static ApplicationPrivilege get(String application, Set name, Collection stored) { + public static Set get(String application, Set name, Collection stored) { if (name.isEmpty()) { - return NONE.apply(application); + return Collections.singleton(NONE.apply(application)); + } else if (application.contains("*")) { + Predicate predicate = Automatons.predicate(application); + final Set result = stored.stream() + .map(ApplicationPrivilegeDescriptor::getApplication) + .filter(predicate) + .distinct() + .map(appName -> resolve(appName, name, stored)) + .collect(Collectors.toSet()); + if (result.isEmpty()) { + return Collections.singleton(resolve(application, name, Collections.emptyMap())); + } else { + return result; + } } else { - Map lookup = stored.stream() - .filter(apd -> apd.getApplication().equals(application)) - .collect(Collectors.toMap(ApplicationPrivilegeDescriptor::getName, Function.identity())); - return resolve(application, name, lookup); + return Collections.singleton(resolve(application, name, stored)); } } + private static ApplicationPrivilege resolve(String application, Set name, Collection stored) { + final Map lookup = stored.stream() + .filter(apd -> apd.getApplication().equals(application)) + .collect(Collectors.toMap(ApplicationPrivilegeDescriptor::getName, Function.identity())); + return resolve(application, name, lookup); + } + private static ApplicationPrivilege resolve(String application, Set names, Map lookup) { final int size = names.size(); if (size == 0) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeDescriptor.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeDescriptor.java index 85d6aad3e3560..613f64f93b54a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeDescriptor.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeDescriptor.java @@ -23,6 +23,8 @@ import java.util.Objects; import java.util.Set; +import static org.elasticsearch.common.Strings.collectionToCommaDelimitedString; + /** * An {@code ApplicationPrivilegeDescriptor} is a representation of a stored {@link ApplicationPrivilege}. * A user (via a role) can be granted an application privilege by name (e.g. ("myapp", "read"). @@ -104,6 +106,11 @@ public XContentBuilder toXContent(XContentBuilder builder, boolean includeTypeFi return builder.endObject(); } + @Override + public String toString() { + return getClass().getSimpleName() + "{[" + application + "],[" + name + "],[" + collectionToCommaDelimitedString(actions) + "]}"; + } + /** * Construct a new {@link ApplicationPrivilegeDescriptor} from XContent. * diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermissionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermissionTests.java index 992ca8db1b083..0f81b872b86d4 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermissionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ApplicationPermissionTests.java @@ -13,15 +13,16 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; public class ApplicationPermissionTests extends ESTestCase { @@ -34,6 +35,7 @@ public class ApplicationPermissionTests extends ESTestCase { private ApplicationPrivilege app1Delete = storePrivilege("app1", "delete", "write/delete"); private ApplicationPrivilege app1Create = storePrivilege("app1", "create", "write/create"); private ApplicationPrivilege app2Read = storePrivilege("app2", "read", "read/*"); + private ApplicationPrivilege otherAppRead = storePrivilege("other-app", "read", "read/*"); private ApplicationPrivilege storePrivilege(String app, String name, String... patterns) { store.add(new ApplicationPrivilegeDescriptor(app, name, Sets.newHashSet(patterns), Collections.emptyMap())); @@ -104,6 +106,16 @@ public void testDoesNotMatchAcrossApplications() { assertThat(buildPermission(app1All, "*").grants(app2Read, "123"), equalTo(false)); } + public void testMatchingWithWildcardApplicationNames() { + final Set readAllApp = ApplicationPrivilege.get("app*", Collections.singleton("read"), store); + assertThat(buildPermission(readAllApp, "*").grants(app1Read, "123"), equalTo(true)); + assertThat(buildPermission(readAllApp, "foo/*").grants(app2Read, "foo/bar"), equalTo(true)); + + assertThat(buildPermission(readAllApp, "*").grants(app1Write, "123"), equalTo(false)); + assertThat(buildPermission(readAllApp, "foo/*").grants(app2Read, "bar/baz"), equalTo(false)); + assertThat(buildPermission(readAllApp, "*").grants(otherAppRead, "abc"), equalTo(false)); + } + public void testMergedPermissionChecking() { final ApplicationPrivilege app1ReadWrite = compositePrivilege("app1", app1Read, app1Write); final ApplicationPermission hasPermission = buildPermission(app1ReadWrite, "allow/*"); @@ -138,16 +150,27 @@ public void testInspectPermissionContents() { } private ApplicationPrivilege actionPrivilege(String appName, String... actions) { - return ApplicationPrivilege.get(appName, Sets.newHashSet(actions), Collections.emptyList()); + final Set privileges = ApplicationPrivilege.get(appName, Sets.newHashSet(actions), Collections.emptyList()); + assertThat(privileges, hasSize(1)); + return privileges.iterator().next(); } private ApplicationPrivilege compositePrivilege(String application, ApplicationPrivilege... children) { Set names = Stream.of(children).map(ApplicationPrivilege::name).flatMap(Set::stream).collect(Collectors.toSet()); - return ApplicationPrivilege.get(application, names, store); + final Set privileges = ApplicationPrivilege.get(application, names, store); + assertThat(privileges, hasSize(1)); + return privileges.iterator().next(); } - private ApplicationPermission buildPermission(ApplicationPrivilege privilege, String... resources) { - return new ApplicationPermission(singletonList(new Tuple<>(privilege, Sets.newHashSet(resources)))); + return buildPermission(Collections.singleton(privilege), resources); + } + + private ApplicationPermission buildPermission(Collection privileges, String... resources) { + final Set resourceSet = Sets.newHashSet(resources); + final List>> privilegesAndResources = privileges.stream() + .map(p -> new Tuple<>(p, resourceSet)) + .collect(Collectors.toList()); + return new ApplicationPermission(privilegesAndResources); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeTests.java index c65f06f05f957..cd917ed81f16e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ApplicationPrivilegeTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.EqualsHashCodeTestUtils; +import org.hamcrest.Matchers; import org.junit.Assert; import java.util.Arrays; @@ -22,9 +23,11 @@ import static org.elasticsearch.common.Strings.collectionToCommaDelimitedString; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.iterableWithSize; public class ApplicationPrivilegeTests extends ESTestCase { @@ -59,6 +62,12 @@ public void testValidationOfApplicationName() { assertNoException(app, () -> ApplicationPrivilege.validateApplicationName(app)); assertNoException(app, () -> ApplicationPrivilege.validateApplicationNameOrWildcard(app)); } + + // wildcards in the suffix + for (String app : Arrays.asList("app1-*", "app1-foo*", "app1-.*", "app1-.foo.*", appNameWithSpecialChars + "*")) { + assertValidationFailure(app, "application name", () -> ApplicationPrivilege.validateApplicationName(app)); + assertNoException(app, () -> ApplicationPrivilege.validateApplicationNameOrWildcard(app)); + } } public void testValidationOfPrivilegeName() { @@ -101,16 +110,23 @@ public void testNonePrivilege() { } public void testGetPrivilegeByName() { - final ApplicationPrivilegeDescriptor descriptor = descriptor("my-app", "read", "data:read/*", "action:login"); + final ApplicationPrivilegeDescriptor myRead = descriptor("my-app", "read", "data:read/*", "action:login"); final ApplicationPrivilegeDescriptor myWrite = descriptor("my-app", "write", "data:write/*", "action:login"); final ApplicationPrivilegeDescriptor myAdmin = descriptor("my-app", "admin", "data:read/*", "action:*"); final ApplicationPrivilegeDescriptor yourRead = descriptor("your-app", "read", "data:read/*", "action:login"); - final Set stored = Sets.newHashSet(descriptor, myWrite, myAdmin, yourRead); + final Set stored = Sets.newHashSet(myRead, myWrite, myAdmin, yourRead); + + final Set myAppRead = ApplicationPrivilege.get("my-app", Collections.singleton("read"), stored); + assertThat(myAppRead, iterableWithSize(1)); + assertPrivilegeEquals(myAppRead.iterator().next(), myRead); - assertEqual(ApplicationPrivilege.get("my-app", Collections.singleton("read"), stored), descriptor); - assertEqual(ApplicationPrivilege.get("my-app", Collections.singleton("write"), stored), myWrite); + final Set myAppWrite = ApplicationPrivilege.get("my-app", Collections.singleton("write"), stored); + assertThat(myAppWrite, iterableWithSize(1)); + assertPrivilegeEquals(myAppWrite.iterator().next(), myWrite); - final ApplicationPrivilege readWrite = ApplicationPrivilege.get("my-app", Sets.newHashSet("read", "write"), stored); + final Set myReadWrite = ApplicationPrivilege.get("my-app", Sets.newHashSet("read", "write"), stored); + assertThat(myReadWrite, Matchers.hasSize(1)); + final ApplicationPrivilege readWrite = myReadWrite.iterator().next(); assertThat(readWrite.getApplication(), equalTo("my-app")); assertThat(readWrite.name(), containsInAnyOrder("read", "write")); assertThat(readWrite.getPatterns(), arrayContainingInAnyOrder("data:read/*", "data:write/*", "action:login")); @@ -124,10 +140,10 @@ public void testGetPrivilegeByName() { } } - private void assertEqual(ApplicationPrivilege myReadPriv, ApplicationPrivilegeDescriptor myRead) { - assertThat(myReadPriv.getApplication(), equalTo(myRead.getApplication())); - assertThat(getPrivilegeName(myReadPriv), equalTo(myRead.getName())); - assertThat(Sets.newHashSet(myReadPriv.getPatterns()), equalTo(myRead.getActions())); + private void assertPrivilegeEquals(ApplicationPrivilege privilege, ApplicationPrivilegeDescriptor descriptor) { + assertThat(privilege.getApplication(), equalTo(descriptor.getApplication())); + assertThat(privilege.name(), contains(descriptor.getName())); + assertThat(Sets.newHashSet(privilege.getPatterns()), equalTo(descriptor.getActions())); } private ApplicationPrivilegeDescriptor descriptor(String application, String name, String... actions) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index 2d1d4a98b4ba6..48659b8968661 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -402,8 +402,8 @@ public static void buildRoleFromDescriptors(Collection roleDescr .flatMap(Collection::stream) .collect(Collectors.toSet()); privilegeStore.getPrivileges(applicationNames, applicationPrivilegeNames, ActionListener.wrap(appPrivileges -> { - applicationPrivilegesMap.forEach((key, names) -> - builder.addApplicationPrivilege(ApplicationPrivilege.get(key.v1(), names, appPrivileges), key.v2())); + applicationPrivilegesMap.forEach((key, names) -> ApplicationPrivilege.get(key.v1(), names, appPrivileges) + .forEach(priv -> builder.addApplicationPrivilege(priv, key.v2()))); listener.onResponse(builder.build()); }, listener::onFailure)); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java index 09c89752f8314..19694bb003314 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java @@ -34,9 +34,11 @@ import org.elasticsearch.common.xcontent.XContentParseException; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.TermQueryBuilder; +import org.elasticsearch.index.query.TermsQueryBuilder; import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.security.ScrollHelper; import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheRequest; @@ -46,6 +48,7 @@ import org.elasticsearch.xpack.security.support.SecurityIndexManager; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -62,6 +65,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.core.ClientHelper.stashWithOrigin; import static org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor.DOC_TYPE_VALUE; +import static org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor.Fields.APPLICATION; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; /** @@ -97,7 +101,7 @@ public void getPrivileges(Collection applications, Collection na listener.onResponse(Collections.emptyList()); } else if (frozenSecurityIndex.isAvailable() == false) { listener.onFailure(frozenSecurityIndex.getUnavailableReason()); - } else if (applications != null && applications.size() == 1 && names != null && names.size() == 1) { + } else if (isSinglePrivilegeMatch(applications, names)) { getPrivilege(Objects.requireNonNull(Iterables.get(applications, 0)), Objects.requireNonNull(Iterables.get(names, 0)), ActionListener.wrap(privilege -> listener.onResponse(privilege == null ? Collections.emptyList() : Collections.singletonList(privilege)), @@ -110,11 +114,14 @@ public void getPrivileges(Collection applications, Collection na if (isEmpty(applications) && isEmpty(names)) { query = typeQuery; } else if (isEmpty(names)) { - query = QueryBuilders.boolQuery().filter(typeQuery).filter( - QueryBuilders.termsQuery(ApplicationPrivilegeDescriptor.Fields.APPLICATION.getPreferredName(), applications)); + query = QueryBuilders.boolQuery().filter(typeQuery).filter(getApplicationNameQuery(applications)); } else if (isEmpty(applications)) { query = QueryBuilders.boolQuery().filter(typeQuery) - .filter(QueryBuilders.termsQuery(ApplicationPrivilegeDescriptor.Fields.NAME.getPreferredName(), names)); + .filter(getPrivilegeNameQuery(names)); + } else if (hasWildcard(applications)) { + query = QueryBuilders.boolQuery().filter(typeQuery) + .filter(getApplicationNameQuery(applications)) + .filter(getPrivilegeNameQuery(names)); } else { final String[] docIds = applications.stream() .flatMap(a -> names.stream().map(n -> toDocId(a, n))) @@ -139,6 +146,49 @@ public void getPrivileges(Collection applications, Collection na } } + private boolean isSinglePrivilegeMatch(Collection applications, Collection names) { + return applications != null && applications.size() == 1 && hasWildcard(applications) == false && names != null && names.size() == 1; + } + + private boolean hasWildcard(Collection applications) { + return applications.stream().anyMatch(n -> n.endsWith("*")); + } + + private QueryBuilder getPrivilegeNameQuery(Collection names) { + return QueryBuilders.termsQuery(ApplicationPrivilegeDescriptor.Fields.NAME.getPreferredName(), names); + } + + private QueryBuilder getApplicationNameQuery(Collection applications) { + if (applications.contains("*")) { + return QueryBuilders.existsQuery(APPLICATION.getPreferredName()); + } + final List rawNames = new ArrayList<>(applications.size()); + final List wildcardNames = new ArrayList<>(applications.size()); + for (String name : applications) { + if (name.endsWith("*")) { + wildcardNames.add(name); + } else { + rawNames.add(name); + } + } + + assert rawNames.isEmpty() == false || wildcardNames.isEmpty() == false; + + TermsQueryBuilder termsQuery = rawNames.isEmpty() ? null : QueryBuilders.termsQuery(APPLICATION.getPreferredName(), rawNames); + if (wildcardNames.isEmpty()) { + return termsQuery; + } + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + if (termsQuery != null) { + boolQuery.filter(termsQuery); + } + for (String wildcard : wildcardNames) { + final String prefix = wildcard.substring(0, wildcard.length() - 1); + boolQuery.filter(QueryBuilders.prefixQuery(APPLICATION.getPreferredName(), prefix)); + } + return boolQuery; + } + private static boolean isEmpty(Collection collection) { return collection == null || collection.isEmpty(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java index 8f60b1d30523f..a833748854943 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java @@ -181,6 +181,45 @@ public void testGetPrivilegesByApplicationName() throws Exception { assertResult(sourcePrivileges, future); } + public void testGetPrivilegesByWildcardApplicationName() throws Exception { + final PlainActionFuture> future = new PlainActionFuture<>(); + store.getPrivileges(Arrays.asList("myapp-*", "yourapp"), null, future); + assertThat(requests, iterableWithSize(1)); + assertThat(requests.get(0), instanceOf(SearchRequest.class)); + SearchRequest request = (SearchRequest) requests.get(0); + assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + + final String query = Strings.toString(request.source().query()); + assertThat(query, containsString("{\"bool\":{\"filter\":[{\"terms\":{\"application\":[\"yourapp\"]")); + assertThat(query, containsString("{\"prefix\":{\"application\":{\"value\":\"myapp-\"")); + assertThat(query, containsString("{\"term\":{\"type\":{\"value\":\"application-privilege\"")); + + final SearchHit[] hits = new SearchHit[0]; + listener.get().onResponse(new SearchResponse(new SearchResponseSections( + new SearchHits(hits, new TotalHits(hits.length, TotalHits.Relation.EQUAL_TO), 0f), + null, null, false, false, null, 1), + "_scrollId1", 1, 1, 0, 1, null, null)); + } + + public void testGetPrivilegesByStarApplicationName() throws Exception { + final PlainActionFuture> future = new PlainActionFuture<>(); + store.getPrivileges(Arrays.asList("*", "anything"), null, future); + assertThat(requests, iterableWithSize(1)); + assertThat(requests.get(0), instanceOf(SearchRequest.class)); + SearchRequest request = (SearchRequest) requests.get(0); + assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + + final String query = Strings.toString(request.source().query()); + assertThat(query, containsString("{\"exists\":{\"field\":\"application\"")); + assertThat(query, containsString("{\"term\":{\"type\":{\"value\":\"application-privilege\"")); + + final SearchHit[] hits = new SearchHit[0]; + listener.get().onResponse(new SearchResponse(new SearchResponseSections( + new SearchHits(hits, new TotalHits(hits.length, TotalHits.Relation.EQUAL_TO), 0f), + null, null, false, false, null, 1), + "_scrollId1", 1, 1, 0, 1, null, null)); + } + public void testGetAllPrivileges() throws Exception { final List sourcePrivileges = Arrays.asList( new ApplicationPrivilegeDescriptor("app1", "admin", newHashSet("action:admin/*", "action:login", "data:read/*"), emptyMap()), diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/20_has_application_privs.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/20_has_application_privs.yml index 85ac286c3f025..eb92cc252b560 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/20_has_application_privs.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/20_has_application_privs.yml @@ -28,6 +28,16 @@ setup: "name": "write", "actions": [ "data:write/*" ] } + }, + "yourapp-v1" : { + "read": { + "actions": [ "data:read/*" ] + } + }, + "yourapp-v2" : { + "read": { + "actions": [ "data:read/*" ] + } } } @@ -83,6 +93,21 @@ setup: } ] } + - do: + security.put_role: + name: "yourapp_read_config" + body: > + { + "cluster": [], + "indices": [], + "applications": [ + { + "application": "yourapp-*", + "privileges": ["read"], + "resources": ["settings/*"] + } + ] + } # And a user for each role - do: @@ -101,6 +126,14 @@ setup: "password": "p@ssw0rd", "roles" : [ "myapp_engineering_write" ] } + - do: + security.put_user: + username: "your_read" + body: > + { + "password": "p@ssw0rd", + "roles" : [ "yourapp_read_config" ] + } --- teardown: @@ -109,6 +142,16 @@ teardown: application: myapp name: "user,read,write" ignore: 404 + - do: + security.delete_privileges: + application: yourapp-v1 + name: "read" + ignore: 404 + - do: + security.delete_privileges: + application: yourapp-v2 + name: "read" + ignore: 404 - do: security.delete_user: @@ -120,6 +163,11 @@ teardown: username: "eng_write" ignore: 404 + - do: + security.delete_user: + username: "your_read" + ignore: 404 + - do: security.delete_role: name: "myapp_engineering_read" @@ -129,6 +177,11 @@ teardown: security.delete_role: name: "myapp_engineering_write" ignore: 404 + + - do: + security.delete_role: + name: "yourapp_read_config" + ignore: 404 --- "Test has_privileges with application-privileges": - do: @@ -188,3 +241,53 @@ teardown: } } } + - do: + headers: { Authorization: "Basic eW91cl9yZWFkOnBAc3N3MHJk" } # your_read + security.has_privileges: + user: null + body: > + { + "application": [ + { + "application" : "yourapp-v1", + "resources" : [ "settings/host", "settings/port", "system/key" ], + "privileges" : [ "data:read/settings", "data:write/settings", "read", "write" ] + }, + { + "application" : "yourapp-v2", + "resources" : [ "settings/host" ], + "privileges" : [ "data:read/settings", "data:write/settings" ] + } + ] + } + + - match: { "username" : "your_read" } + - match: { "has_all_requested" : false } + - match: { "application": { + "yourapp-v1": { + "settings/host": { + "data:read/settings": true, + "data:write/settings": false, + "read": true, + "write": false + }, + "settings/port": { + "data:read/settings": true, + "data:write/settings": false, + "read": true, + "write": false + }, + "system/key": { + "data:read/settings": false, + "data:write/settings": false, + "read": false, + "write": false + } + }, + "yourapp-v2": { + "settings/host": { + "data:read/settings": true, + "data:write/settings": false, + } + } + } }