diff --git a/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java index f871e131b9..4b0ef62b49 100644 --- a/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java +++ b/src/integrationTest/java/org/opensearch/security/ConfigurationFiles.java @@ -11,13 +11,10 @@ import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import java.util.Objects; -import org.opensearch.core.common.Strings; import org.opensearch.security.securityconf.impl.CType; public class ConfigurationFiles { @@ -43,14 +40,6 @@ public static Path createConfigurationDirectory() { } } - public static void writeToConfig(final CType cType, final Path configFolder, final String content) throws IOException { - if (Strings.isNullOrEmpty(content)) return; - try (final var out = Files.newOutputStream(cType.configFile(configFolder), StandardOpenOption.APPEND)) { - out.write(content.getBytes(StandardCharsets.UTF_8)); - out.flush(); - } - } - public static void copyResourceToFile(String resource, Path destination) { try (InputStream input = ConfigurationFiles.class.getClassLoader().getResourceAsStream(resource)) { Objects.requireNonNull(input, "Cannot find source resource " + resource); diff --git a/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java index 00c0cf4f07..b1dabff199 100644 --- a/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java @@ -11,8 +11,6 @@ package org.opensearch.security.api; -import java.io.IOException; -import java.nio.file.Path; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,29 +18,17 @@ import com.carrotsearch.randomizedtesting.RandomizedTest; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; -import org.apache.commons.io.FileUtils; import org.apache.http.HttpStatus; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.awaitility.Awaitility; -import org.junit.AfterClass; -import org.junit.Before; import org.junit.runner.RunWith; -import org.opensearch.common.CheckedConsumer; import org.opensearch.common.CheckedSupplier; import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.security.ConfigurationFiles; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.security.hasher.PasswordHasher; import org.opensearch.security.hasher.PasswordHasherFactory; -import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.TestSecurityConfig; -import org.opensearch.test.framework.certificate.CertificateData; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; @@ -52,7 +38,6 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.notNullValue; import static org.opensearch.security.CrossClusterSearchTests.PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED; import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; @@ -61,21 +46,28 @@ import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.RELOAD_CERTS_ACTION; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE; -import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX; -import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE; -import static org.opensearch.test.framework.TestSecurityConfig.REST_ADMIN_REST_API_ACCESS; @ThreadLeakScope(ThreadLeakScope.Scope.NONE) @RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) public abstract class AbstractApiIntegrationTest extends RandomizedTest { - private static final Logger LOGGER = LogManager.getLogger(TestSecurityConfig.class); - - public static final String NEW_USER = "new-user"; + public static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles( + new TestSecurityConfig.Role("all_access").clusterPermissions("*").indexPermissions("*").on("*") + ); + public static final TestSecurityConfig.User REST_ADMIN_USER = new TestSecurityConfig.User("rest-api-admin").roles( + new TestSecurityConfig.Role("role").clusterPermissions(allRestAdminPermissions()) + ); - public static final String REST_ADMIN_USER = "rest-api-admin"; + public static final TestSecurityConfig.Role REST_ADMIN_REST_API_ACCESS_ROLE = new TestSecurityConfig.Role( + "rest_admin__rest_api_access" + ); + public static final TestSecurityConfig.Role EXAMPLE_ROLE = new TestSecurityConfig.Role("example_role").indexPermissions("crud") + .on("example_index"); - public static final String ADMIN_USER_NAME = "admin"; + /** + * A user without any privileges + */ + public static final TestSecurityConfig.User NEW_USER = new TestSecurityConfig.User("new-user"); public static final String DEFAULT_PASSWORD = "secret"; @@ -85,121 +77,23 @@ public abstract class AbstractApiIntegrationTest extends RandomizedTest { Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT).build() ); - public static Path configurationFolder; - - protected static TestSecurityConfig testSecurityConfig = new TestSecurityConfig(); - - public static LocalCluster localCluster; - - private Class testClass; - - @Before - public void startCluster() throws IOException { - if (this.getClass().equals(testClass)) { - return; - } - configurationFolder = ConfigurationFiles.createConfigurationDirectory(); - extendConfiguration(); - final var clusterManager = randomFrom(List.of(ClusterManager.THREE_CLUSTER_MANAGERS, ClusterManager.SINGLENODE)); - final var localClusterBuilder = new LocalCluster.Builder().clusterManager(clusterManager) + protected static LocalCluster.Builder clusterBuilder() { + return new LocalCluster.Builder().clusterManager(ClusterManager.DEFAULT) .nodeSettings(getClusterSettings()) - .defaultConfigurationInitDirectory(configurationFolder.toString()) - .loadConfigurationIntoIndex(false); - localCluster = localClusterBuilder.build(); - localCluster.before(); - try (TestRestClient client = localCluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) { - Awaitility.await() - .alias("Load default configuration") - .until(() -> client.securityHealth().getTextFromJsonBody("/status"), equalTo("UP")); - } - testClass = this.getClass(); + .authc(TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL) + .users(ADMIN_USER, REST_ADMIN_USER, NEW_USER) + .roles(EXAMPLE_ROLE, REST_ADMIN_REST_API_ACCESS_ROLE); } - protected Map getClusterSettings() { + protected static Map getClusterSettings() { Map clusterSettings = new HashMap<>(); - clusterSettings.put(SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, true); - clusterSettings.put(PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_admin__all_access", REST_ADMIN_REST_API_ACCESS)); - clusterSettings.put(SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE, randomBoolean()); + clusterSettings.put( + PLUGINS_SECURITY_RESTAPI_ROLES_ENABLED, + List.of("user_admin__all_access", REST_ADMIN_REST_API_ACCESS_ROLE.getName(), "user_rest-api-admin__role") + ); return clusterSettings; } - private static void extendConfiguration() throws IOException { - extendActionGroups(configurationFolder, testSecurityConfig.actionGroups()); - extendRoles(configurationFolder, testSecurityConfig.roles()); - extendRolesMapping(configurationFolder, testSecurityConfig.rolesMapping()); - extendUsers(configurationFolder, testSecurityConfig.getUsers()); - } - - private static void extendUsers(final Path configFolder, final List users) throws IOException { - if (users == null) return; - if (users.isEmpty()) return; - LOGGER.info("Adding users to the default configuration: "); - try (final var contentBuilder = XContentFactory.yamlBuilder()) { - contentBuilder.startObject(); - for (final var u : users) { - LOGGER.info("\t\t - {}", u.getName()); - contentBuilder.field(u.getName()); - u.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); - } - contentBuilder.endObject(); - ConfigurationFiles.writeToConfig(CType.INTERNALUSERS, configFolder, removeDashes(contentBuilder.toString())); - } - } - - private static void extendActionGroups(final Path configFolder, final List actionGroups) - throws IOException { - if (actionGroups == null) return; - if (actionGroups.isEmpty()) return; - LOGGER.info("Adding action groups to the default configuration: "); - try (final var contentBuilder = XContentFactory.yamlBuilder()) { - contentBuilder.startObject(); - for (final var ag : actionGroups) { - LOGGER.info("\t\t - {}", ag.name()); - contentBuilder.field(ag.name()); - ag.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); - } - contentBuilder.endObject(); - ConfigurationFiles.writeToConfig(CType.ACTIONGROUPS, configFolder, removeDashes(contentBuilder.toString())); - } - } - - private static void extendRoles(final Path configFolder, final List roles) throws IOException { - if (roles == null) return; - if (roles.isEmpty()) return; - LOGGER.info("Adding roles to the default configuration: "); - try (final var contentBuilder = XContentFactory.yamlBuilder()) { - contentBuilder.startObject(); - for (final var r : roles) { - LOGGER.info("\t\t - {}", r.getName()); - contentBuilder.field(r.getName()); - r.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); - } - contentBuilder.endObject(); - ConfigurationFiles.writeToConfig(CType.ROLES, configFolder, removeDashes(contentBuilder.toString())); - } - } - - private static void extendRolesMapping(final Path configFolder, final List rolesMapping) - throws IOException { - if (rolesMapping == null) return; - if (rolesMapping.isEmpty()) return; - LOGGER.info("Adding roles mapping to the default configuration: "); - try (final var contentBuilder = XContentFactory.yamlBuilder()) { - contentBuilder.startObject(); - for (final var rm : rolesMapping) { - LOGGER.info("\t\t - {}", rm.name()); - contentBuilder.field(rm.name()); - rm.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); - } - contentBuilder.endObject(); - ConfigurationFiles.writeToConfig(CType.ROLESMAPPING, configFolder, removeDashes(contentBuilder.toString())); - } - } - - private static String removeDashes(final String content) { - return content.replace("---", ""); - } - protected static String[] allRestAdminPermissions() { final var permissions = new String[ENDPOINTS_WITH_PERMISSIONS.size() + 1]; // 1 additional action for SSL update certs var counter = 0; @@ -233,42 +127,6 @@ protected String randomRestAdminPermission() { return randomFrom(permissions); } - @AfterClass - public static void stopCluster() throws IOException { - if (localCluster != null) localCluster.close(); - FileUtils.deleteDirectory(configurationFolder.toFile()); - } - - protected void withUser(final String user, final CheckedConsumer restClientHandler) throws Exception { - withUser(user, DEFAULT_PASSWORD, restClientHandler); - } - - protected void withUser(final String user, final String password, final CheckedConsumer restClientHandler) - throws Exception { - try (TestRestClient client = localCluster.getRestClient(user, password)) { - restClientHandler.accept(client); - } - } - - protected void withUser( - final String user, - final CertificateData certificateData, - final CheckedConsumer restClientHandler - ) throws Exception { - withUser(user, DEFAULT_PASSWORD, certificateData, restClientHandler); - } - - protected void withUser( - final String user, - final String password, - final CertificateData certificateData, - final CheckedConsumer restClientHandler - ) throws Exception { - try (final TestRestClient client = localCluster.getRestClient(user, password, certificateData)) { - restClientHandler.accept(client); - } - } - protected String apiPathPrefix() { return randomFrom(List.of(LEGACY_OPENDISTRO_PREFIX, PLUGINS_PREFIX)); } @@ -298,18 +156,6 @@ protected String apiPath(final String... path) { return fullPath.toString(); } - void badRequestWithReason(final CheckedSupplier endpointCallback, final String expectedMessage) - throws Exception { - final var response = badRequest(endpointCallback); - assertThat(response.getBody(), response.getTextFromJsonBody("/reason"), is(expectedMessage)); - } - - void badRequestWithMessage(final CheckedSupplier endpointCallback, final String expectedMessage) - throws Exception { - final var response = badRequest(endpointCallback); - assertThat(response.getBody(), response.getTextFromJsonBody("/message"), is(expectedMessage)); - } - public static TestRestClient.HttpResponse badRequest(final CheckedSupplier endpointCallback) throws Exception { final var response = endpointCallback.get(); @@ -318,14 +164,6 @@ public static TestRestClient.HttpResponse badRequest(final CheckedSupplier endpointCallback) throws Exception { - final var response = endpointCallback.get(); - assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); - assertResponseBody(response.getBody()); - assertThat(response.getBody(), response.getTextFromJsonBody("/status"), equalToIgnoringCase("created")); - return response; - } - public static void forbidden( final CheckedSupplier endpointCallback, final String expectedMessage @@ -342,14 +180,6 @@ public static TestRestClient.HttpResponse forbidden(final CheckedSupplier endpointCallback) - throws Exception { - final var response = endpointCallback.get(); - assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_METHOD_NOT_ALLOWED)); - assertResponseBody(response.getBody()); - return response; - } - public static TestRestClient.HttpResponse notImplemented(final CheckedSupplier endpointCallback) throws Exception { final var response = endpointCallback.get(); @@ -366,12 +196,6 @@ public static TestRestClient.HttpResponse notFound(final CheckedSupplier endpointCallback, final String expectedMessage) - throws Exception { - final var response = notFound(endpointCallback); - assertThat(response.getBody(), response.getTextFromJsonBody("/message"), is(expectedMessage)); - } - public static TestRestClient.HttpResponse ok(final CheckedSupplier endpointCallback) throws Exception { final var response = endpointCallback.get(); @@ -380,24 +204,6 @@ public static TestRestClient.HttpResponse ok(final CheckedSupplier endpointCallback, - final String expectedMessage - ) throws Exception { - final var response = endpointCallback.get(); - assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_OK)); - assertResponseBody(response.getBody(), expectedMessage); - return response; - } - - TestRestClient.HttpResponse unauthorized(final CheckedSupplier endpointCallback) - throws Exception { - final var response = endpointCallback.get(); - assertThat(response.getBody(), response.getStatusCode(), equalTo(HttpStatus.SC_UNAUTHORIZED)); - assertResponseBody(response.getBody()); - return response; - } - public static void assertResponseBody(final String responseBody) { assertThat(responseBody, notNullValue()); assertThat(responseBody, not(equalTo(""))); diff --git a/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java index a6d6902359..16031a8d42 100644 --- a/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java @@ -16,10 +16,10 @@ import java.util.StringJoiner; import org.hamcrest.Matcher; -import org.junit.Test; import org.opensearch.common.CheckedSupplier; import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import com.nimbusds.jose.util.Pair; @@ -27,7 +27,6 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.oneOf; import static org.opensearch.security.api.PatchPayloadHelper.addOp; @@ -35,18 +34,16 @@ import static org.opensearch.security.api.PatchPayloadHelper.removeOp; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public abstract class AbstractConfigEntityApiIntegrationTest extends AbstractApiIntegrationTest { - static { - testSecurityConfig.withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions()); - } - - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(SECURITY_RESTAPI_ADMIN_ENABLED, true); - return clusterSettings; + protected static LocalCluster.Builder clusterBuilder() { + return AbstractApiIntegrationTest.clusterBuilder().nodeSetting(SECURITY_RESTAPI_ADMIN_ENABLED, true); } interface TestDescriptor { @@ -99,26 +96,24 @@ protected String apiPath(String... paths) { return fullPath.toString(); } - @Test - public void forbiddenForRegularUsers() throws Exception { - withUser(NEW_USER, client -> { - forbidden(() -> client.putJson(apiPath("some_entity"), EMPTY_BODY)); - forbidden(() -> client.get(apiPath())); - forbidden(() -> client.get(apiPath("some_entity"))); - forbidden(() -> client.putJson(apiPath("some_entity"), EMPTY_BODY)); - forbidden(() -> client.patch(apiPath(), EMPTY_BODY)); - forbidden(() -> client.patch(apiPath("some_entity"), EMPTY_BODY)); - forbidden(() -> client.delete(apiPath("some_entity"))); - }); + public void forbiddenForRegularUsers(LocalCluster localCluster) throws Exception { + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.putJson(apiPath("some_entity"), EMPTY_BODY), isForbidden()); + assertThat(client.get(apiPath()), isForbidden()); + assertThat(client.get(apiPath("some_entity")), isForbidden()); + assertThat(client.putJson(apiPath("some_entity"), EMPTY_BODY), isForbidden()); + assertThat(client.patch(apiPath(), EMPTY_BODY), isForbidden()); + assertThat(client.patch(apiPath("some_entity"), EMPTY_BODY), isForbidden()); + assertThat(client.delete(apiPath("some_entity")), isForbidden()); + } } - @Test - public void availableForAdminUser() throws Exception { - final var entitiesNames = predefinedHiddenAndReservedConfigEntities(); + public void availableForAdminUser(LocalCluster localCluster) throws Exception { + final var entitiesNames = predefinedHiddenAndReservedConfigEntities(localCluster); final var hiddenEntityName = entitiesNames.getLeft(); final var reservedEntityName = entitiesNames.getRight(); // can't see hidden resources - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { verifyNoHiddenEntities(() -> client.get(apiPath())); creationOfReadOnlyEntityForbidden( randomAsciiAlphanumOfLength(10), @@ -131,35 +126,28 @@ public void availableForAdminUser() throws Exception { verifyUpdateAndDeleteReservedConfigEntityForbidden(reservedEntityName, client); verifyCrudOperations(null, null, client); verifyBadRequestOperations(client); - }); + } } - Pair predefinedHiddenAndReservedConfigEntities() throws Exception { + Pair predefinedHiddenAndReservedConfigEntities(LocalCluster localCluster) throws Exception { final var hiddenEntityName = randomAsciiAlphanumOfLength(10); final var reservedEntityName = randomAsciiAlphanumOfLength(10); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created(() -> client.putJson(apiPath(hiddenEntityName), testDescriptor.hiddenEntityPayload())) - ); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created(() -> client.putJson(apiPath(reservedEntityName), testDescriptor.reservedEntityPayload())) - ); + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.putJson(apiPath(hiddenEntityName), testDescriptor.hiddenEntityPayload()), isCreated()); + assertThat(client.putJson(apiPath(reservedEntityName), testDescriptor.reservedEntityPayload()), isCreated()); + } return Pair.of(hiddenEntityName, reservedEntityName); } - @Test - public void availableForTLSAdminUser() throws Exception { - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::availableForSuperAdminUser); + public void availableForTLSAdminUser(LocalCluster localCluster) throws Exception { + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + availableForSuperAdminUser(client); + } } - @Test - public void availableForRESTAdminUser() throws Exception { - withUser(REST_ADMIN_USER, this::availableForSuperAdminUser); - if (testDescriptor.restAdminLimitedUser().isPresent()) { - withUser(testDescriptor.restAdminLimitedUser().get(), this::availableForSuperAdminUser); + public void availableForRESTAdminUser(LocalCluster localCluster) throws Exception { + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + availableForSuperAdminUser(client); } } @@ -178,7 +166,9 @@ void availableForSuperAdminUser(final TestRestClient client) throws Exception { } void verifyNoHiddenEntities(final CheckedSupplier endpointCallback) throws Exception { - final var body = ok(endpointCallback).bodyAsJsonNode(); + final var resp = endpointCallback.get(); + assertThat(resp, isOk()); + final var body = resp.bodyAsJsonNode(); final var pretty = body.toPrettyString(); final var it = body.elements(); while (it.hasNext()) { @@ -190,11 +180,11 @@ void verifyNoHiddenEntities(final CheckedSupplier client.putJson(apiPath(entityName), configEntity)), - is(oneOf("static", "hidden", "reserved")) - ); - badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(10), configEntity)))); + final var resp = client.putJson(apiPath(entityName), configEntity); + assertThat(resp, isBadRequest()); + assertInvalidKeys(resp, is(oneOf("static", "hidden", "reserved"))); + final var resp2 = client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(10), configEntity))); + assertThat(resp2, isBadRequest()); } } @@ -222,54 +212,60 @@ void assertWrongDataType(final TestRestClient.HttpResponse response, final Map client.putJson(apiPath(hiddenEntityName), testDescriptor.entityPayload()), expectedErrorMessage); - notFound( - () -> client.patch( + assertThat( + client.putJson(apiPath(hiddenEntityName), testDescriptor.entityPayload()), + isNotFound().withAttribute("/message", expectedErrorMessage) + ); + assertThat( + client.patch( apiPath(hiddenEntityName), patch(replaceOp(testDescriptor.entityJsonProperty(), testDescriptor.jsonPropertyPayload())) ), - expectedErrorMessage + isNotFound().withAttribute("/message", expectedErrorMessage) ); - notFound(() -> client.patch(apiPath(), patch(replaceOp(hiddenEntityName, testDescriptor.entityPayload()))), expectedErrorMessage); - notFound(() -> client.patch(apiPath(hiddenEntityName), patch(removeOp(testDescriptor.entityJsonProperty()))), expectedErrorMessage); - notFound(() -> client.patch(apiPath(), patch(removeOp(hiddenEntityName))), expectedErrorMessage); - notFound(() -> client.delete(apiPath(hiddenEntityName)), expectedErrorMessage); + assertThat( + client.patch(apiPath(), patch(replaceOp(hiddenEntityName, testDescriptor.entityPayload()))), + isNotFound().withAttribute("/message", expectedErrorMessage) + ); + assertThat( + client.patch(apiPath(hiddenEntityName), patch(removeOp(testDescriptor.entityJsonProperty()))), + isNotFound().withAttribute("/message", expectedErrorMessage) + ); + assertThat( + client.patch(apiPath(), patch(removeOp(hiddenEntityName))), + isNotFound().withAttribute("/message", expectedErrorMessage) + ); + assertThat(client.delete(apiPath(hiddenEntityName)), isNotFound().withAttribute("/message", expectedErrorMessage)); } void verifyUpdateAndDeleteReservedConfigEntityForbidden(final String reservedEntityName, final TestRestClient client) throws Exception { final var expectedErrorMessage = "Resource '" + reservedEntityName + "' is reserved."; - forbidden(() -> client.putJson(apiPath(reservedEntityName), testDescriptor.entityPayload()), expectedErrorMessage); - forbidden( - () -> client.patch( + assertThat( + client.putJson(apiPath(reservedEntityName), testDescriptor.entityPayload()), + isForbidden().withAttribute("/message", expectedErrorMessage) + ); + assertThat( + client.patch( apiPath(reservedEntityName), patch(replaceOp(testDescriptor.entityJsonProperty(), testDescriptor.entityJsonProperty())) ), - expectedErrorMessage + isForbidden().withAttribute("/message", expectedErrorMessage) ); - forbidden( - () -> client.patch(apiPath(), patch(replaceOp(reservedEntityName, testDescriptor.entityPayload()))), - expectedErrorMessage + assertThat( + client.patch(apiPath(), patch(replaceOp(reservedEntityName, testDescriptor.entityPayload()))), + isForbidden().withAttribute("/message", expectedErrorMessage) + ); + assertThat( + client.patch(apiPath(), patch(removeOp(reservedEntityName))), + isForbidden().withAttribute("/message", expectedErrorMessage) ); - forbidden(() -> client.patch(apiPath(), patch(removeOp(reservedEntityName))), expectedErrorMessage); - forbidden( - () -> client.patch(apiPath(reservedEntityName), patch(removeOp(testDescriptor.entityJsonProperty()))), - expectedErrorMessage + assertThat( + client.patch(apiPath(reservedEntityName), patch(removeOp(testDescriptor.entityJsonProperty()))), + isForbidden().withAttribute("/message", expectedErrorMessage) ); - forbidden(() -> client.delete(apiPath(reservedEntityName)), expectedErrorMessage); + assertThat(client.delete(apiPath(reservedEntityName)), isForbidden().withAttribute("/message", expectedErrorMessage)); } void forbiddenToCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception {} diff --git a/src/integrationTest/java/org/opensearch/security/api/AccountRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/AccountRestApiIntegrationTest.java index 65bec9f788..8af5910d19 100644 --- a/src/integrationTest/java/org/opensearch/security/api/AccountRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/AccountRestApiIntegrationTest.java @@ -10,16 +10,25 @@ package org.opensearch.security.api; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.not; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; +import static org.opensearch.test.framework.matcher.RestMatchers.isUnauthorized; public class AccountRestApiIntegrationTest extends AbstractApiIntegrationTest { @@ -33,11 +42,12 @@ public class AccountRestApiIntegrationTest extends AbstractApiIntegrationTest { public final static String TEST_USER_NEW_PASSWORD = randomAlphabetic(10); - static { - testSecurityConfig.user(new TestSecurityConfig.User(TEST_USER).password(TEST_USER_PASSWORD)) - .user(new TestSecurityConfig.User(RESERVED_USER).reserved(true)) - .user(new TestSecurityConfig.User(HIDDEN_USERS).hidden(true)); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users( + new TestSecurityConfig.User(TEST_USER).password(TEST_USER_PASSWORD), + new TestSecurityConfig.User(RESERVED_USER).reserved(true), + new TestSecurityConfig.User(HIDDEN_USERS).hidden(true) + ).build(); private String accountPath() { return super.apiPath("account"); @@ -45,10 +55,11 @@ private String accountPath() { @Test public void accountInfo() throws Exception { - withUser(NEW_USER, client -> { - var response = ok(() -> client.get(accountPath())); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isOk()); final var account = response.bodyAsJsonNode(); - assertThat(response.getBody(), account.get("user_name").asText(), is(NEW_USER)); + assertThat(response.getBody(), account.get("user_name").asText(), is(NEW_USER.getName())); assertThat(response.getBody(), not(account.get("is_reserved").asBoolean())); assertThat(response.getBody(), not(account.get("is_hidden").asBoolean())); assertThat(response.getBody(), account.get("is_internal_user").asBoolean()); @@ -57,69 +68,77 @@ public void accountInfo() throws Exception { assertThat(response.getBody(), account.get("custom_attribute_names").isArray()); assertThat(response.getBody(), account.get("tenants").isObject()); assertThat(response.getBody(), account.get("roles").isArray()); - }); - withUser(NEW_USER, "a", client -> unauthorized(() -> client.get(accountPath()))); - withUser("a", "b", client -> unauthorized(() -> client.get(accountPath()))); + } + try (TestRestClient client = localCluster.getRestClient(NEW_USER.getName(), "a")) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isUnauthorized()); + } + try (TestRestClient client = localCluster.getRestClient("a", "b")) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isUnauthorized()); + } } @Test public void changeAccountPassword() throws Exception { - withUser(TEST_USER, TEST_USER_PASSWORD, this::verifyWrongPayload); + try (TestRestClient client = localCluster.getRestClient(TEST_USER, TEST_USER_PASSWORD)) { + verifyWrongPayload(client); + } verifyPasswordCanBeChanged(); - withUser(RESERVED_USER, client -> { - var response = ok(() -> client.get(accountPath())); - assertThat(response.getBody(), response.getBooleanFromJsonBody("/is_reserved")); - forbidden(() -> client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10)))); - }); - withUser(HIDDEN_USERS, client -> { - var response = ok(() -> client.get(accountPath())); - assertThat(response.getBody(), response.getBooleanFromJsonBody("/is_hidden")); - notFound(() -> client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10)))); - }); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> { - ok(() -> client.get(accountPath())); - notFound(() -> client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10)))); - }); + try (TestRestClient client = localCluster.getRestClient(RESERVED_USER, DEFAULT_PASSWORD)) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isOk()); + assertThat(response.getBooleanFromJsonBody("/is_reserved"), is(true)); + assertThat(client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10))), isForbidden()); + } + try (TestRestClient client = localCluster.getRestClient(HIDDEN_USERS, DEFAULT_PASSWORD)) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isOk()); + assertThat(response.getBooleanFromJsonBody("/is_hidden"), is(true)); + assertThat(client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10))), isNotFound()); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + HttpResponse response = client.get(accountPath()); + assertThat(response, isOk()); + assertThat(client.putJson(accountPath(), changePasswordPayload(DEFAULT_PASSWORD, randomAlphabetic(10))), isNotFound()); + } } private void verifyWrongPayload(final TestRestClient client) throws Exception { - badRequest(() -> client.putJson(accountPath(), EMPTY_BODY)); - badRequest(() -> client.putJson(accountPath(), changePasswordPayload(null, "new_password"))); - badRequest(() -> client.putJson(accountPath(), changePasswordPayload("wrong-password", "some_new_pwd"))); - badRequest(() -> client.putJson(accountPath(), changePasswordPayload(TEST_USER_PASSWORD, null))); - badRequest(() -> client.putJson(accountPath(), changePasswordPayload(TEST_USER_PASSWORD, ""))); - badRequest(() -> client.putJson(accountPath(), changePasswordWithHashPayload(TEST_USER_PASSWORD, null))); - badRequest(() -> client.putJson(accountPath(), changePasswordWithHashPayload(TEST_USER_PASSWORD, ""))); - badRequest( - () -> client.putJson( + assertThat(client.putJson(accountPath(), EMPTY_BODY), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordPayload(null, "new_password")), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordPayload("wrong-password", "some_new_pwd")), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordPayload(TEST_USER_PASSWORD, null)), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordPayload(TEST_USER_PASSWORD, "")), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordWithHashPayload(TEST_USER_PASSWORD, null)), isBadRequest()); + assertThat(client.putJson(accountPath(), changePasswordWithHashPayload(TEST_USER_PASSWORD, "")), isBadRequest()); + assertThat( + client.putJson( accountPath(), (builder, params) -> builder.startObject() .field("current_password", TEST_USER_PASSWORD) .startArray("backend_roles") .endArray() .endObject() - ) + ), + isBadRequest() ); } private void verifyPasswordCanBeChanged() throws Exception { final var newPassword = randomAlphabetic(10); - withUser( - TEST_USER, - TEST_USER_PASSWORD, - client -> ok( - () -> client.putJson( - accountPath(), - changePasswordWithHashPayload(TEST_USER_PASSWORD, passwordHasher.hash(newPassword.toCharArray())) - ) - ) - ); - withUser( - TEST_USER, - newPassword, - client -> ok(() -> client.putJson(accountPath(), changePasswordPayload(newPassword, TEST_USER_NEW_PASSWORD))) - ); + try (TestRestClient client = localCluster.getRestClient(TEST_USER, TEST_USER_PASSWORD)) { + HttpResponse resp = client.putJson( + accountPath(), + changePasswordWithHashPayload(TEST_USER_PASSWORD, passwordHasher.hash(newPassword.toCharArray())) + ); + assertThat(resp, isOk()); + } + try (TestRestClient client = localCluster.getRestClient(TEST_USER, newPassword)) { + HttpResponse resp = client.putJson(accountPath(), changePasswordPayload(newPassword, TEST_USER_NEW_PASSWORD)); + assertThat(resp, isOk()); + } } @Test @@ -127,10 +146,9 @@ public void testPutAccountRetainsAccountInformation() throws Exception { final var username = "test"; final String password = randomAlphabetic(10); final String newPassword = randomAlphabetic(10); - withUser( - ADMIN_USER_NAME, - client -> created( - () -> client.putJson( + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat( + client.putJson( apiPath("internalusers", username), (builder, params) -> builder.startObject() .field("password", password) @@ -140,24 +158,29 @@ public void testPutAccountRetainsAccountInformation() throws Exception { .endArray() .field("opendistro_security_roles") .startArray() - .value("user_limited-user__limited-role") + .value(EXAMPLE_ROLE.getName()) .endArray() .field("attributes") .startObject() .field("foo", "bar") .endObject() .endObject() - ) - ) - ); - withUser(username, password, client -> ok(() -> client.putJson(accountPath(), changePasswordPayload(password, newPassword)))); - withUser(ADMIN_USER_NAME, client -> { - final var response = ok(() -> client.get(apiPath("internalusers", username))); + ), + isCreated() + ); + } + try (TestRestClient client = localCluster.getRestClient(username, password)) { + HttpResponse resp = client.putJson(accountPath(), changePasswordPayload(password, newPassword)); + assertThat(resp, isOk()); + } + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + HttpResponse response = client.get(apiPath("internalusers", username)); + assertThat(response, isOk()); final var user = response.bodyAsJsonNode().get(username); assertThat(user.toPrettyString(), user.get("backend_roles").get(0).asText(), is("test-backend-role")); - assertThat(user.toPrettyString(), user.get("opendistro_security_roles").get(0).asText(), is("user_limited-user__limited-role")); + assertThat(user.toPrettyString(), user.get("opendistro_security_roles").get(0).asText(), is(EXAMPLE_ROLE.getName())); assertThat(user.toPrettyString(), user.get("attributes").get("foo").asText(), is("bar")); - }); + } } private ToXContentObject changePasswordPayload(final String currentPassword, final String newPassword) { diff --git a/src/integrationTest/java/org/opensearch/security/api/ActionGroupsRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/ActionGroupsRestApiIntegrationTest.java index 174c2b4ea6..bb47efefec 100644 --- a/src/integrationTest/java/org/opensearch/security/api/ActionGroupsRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/ActionGroupsRestApiIntegrationTest.java @@ -12,12 +12,15 @@ package org.opensearch.security.api; import java.util.List; -import java.util.Map; import java.util.Optional; +import org.junit.ClassRule; +import org.junit.Test; + import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.CoreMatchers.is; @@ -28,6 +31,11 @@ import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.removeOp; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class ActionGroupsRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { @@ -35,16 +43,20 @@ public class ActionGroupsRestApiIntegrationTest extends AbstractConfigEntityApiI private final static String REST_ADMIN_PERMISSION_ACTION_GROUP = "rest-admin-permissions-action-group"; - static { - testSecurityConfig.withRestAdminUser(REST_API_ADMIN_ACTION_GROUPS_ONLY, restAdminPermission(Endpoint.ACTIONGROUPS)) - .actionGroups( - new TestSecurityConfig.ActionGroup( - REST_ADMIN_PERMISSION_ACTION_GROUP, - TestSecurityConfig.ActionGroup.Type.INDEX, - allRestAdminPermissions() - ) - ); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users( + new TestSecurityConfig.User(REST_API_ADMIN_ACTION_GROUPS_ONLY).roles( + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.ACTIONGROUPS)) + ) + ) + .actionGroups( + new TestSecurityConfig.ActionGroup( + REST_ADMIN_PERMISSION_ACTION_GROUP, + TestSecurityConfig.ActionGroup.Type.INDEX, + allRestAdminPermissions() + ) + ) + .build(); public ActionGroupsRestApiIntegrationTest() { super("actiongroups", new TestDescriptor() { @@ -111,74 +123,117 @@ static String randomType() { return randomFrom(List.of(TestSecurityConfig.ActionGroup.Type.CLUSTER.type(), TestSecurityConfig.ActionGroup.Type.INDEX.type())); } + @Test + public void forbiddenForRegularUsers() throws Exception { + super.forbiddenForRegularUsers(localCluster); + } + + @Test + public void availableForAdminUser() throws Exception { + super.availableForAdminUser(localCluster); + } + + @Test + public void availableForTLSAdminUser() throws Exception { + super.availableForTLSAdminUser(localCluster); + } + + @Test + public void availableForRESTAdminUser() throws Exception { + super.availableForRESTAdminUser(localCluster); + } + @Override void forbiddenToCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception { - forbidden(() -> client.putJson(apiPath("new_rest_admin_action_group"), actionGroup(randomRestAdminPermission()))); - forbidden(() -> client.patch(apiPath(), patch(addOp("new_rest_admin_action_group", actionGroup(randomRestAdminPermission()))))); + assertThat(client.putJson(apiPath("new_rest_admin_action_group"), actionGroup(randomRestAdminPermission())), isForbidden()); + assertThat( + client.patch(apiPath(), patch(addOp("new_rest_admin_action_group", actionGroup(randomRestAdminPermission())))), + isForbidden() + ); } @Override void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final TestRestClient client) throws Exception { // update - forbidden(() -> client.putJson(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), actionGroup())); - forbidden(() -> client.patch(apiPath(), patch(replaceOp(REST_ADMIN_PERMISSION_ACTION_GROUP, actionGroup("a", "b"))))); - forbidden( - () -> client.patch(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), patch(replaceOp("allowed_actions", configJsonArray("c", "d")))) + assertThat(client.putJson(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), actionGroup()), isForbidden()); + assertThat(client.patch(apiPath(), patch(replaceOp(REST_ADMIN_PERMISSION_ACTION_GROUP, actionGroup("a", "b")))), isForbidden()); + assertThat( + client.patch(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), patch(replaceOp("allowed_actions", configJsonArray("c", "d")))), + isForbidden() ); // remove - forbidden(() -> client.patch(apiPath(), patch(removeOp(REST_ADMIN_PERMISSION_ACTION_GROUP)))); - forbidden(() -> client.patch(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), patch(removeOp("allowed_actions")))); - forbidden(() -> client.delete(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP))); + assertThat(client.patch(apiPath(), patch(removeOp(REST_ADMIN_PERMISSION_ACTION_GROUP))), isForbidden()); + assertThat(client.patch(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP), patch(removeOp("allowed_actions"))), isForbidden()); + assertThat(client.delete(apiPath(REST_ADMIN_PERMISSION_ACTION_GROUP)), isForbidden()); } @Override void verifyCrudOperations(final Boolean hidden, final Boolean reserved, final TestRestClient client) throws Exception { - created(() -> client.putJson(apiPath("new_action_group"), actionGroup(hidden, reserved, "a", "b"))); - assertActionGroup(ok(() -> client.get(apiPath("new_action_group"))), "new_action_group", List.of("a", "b")); - - ok(() -> client.putJson(apiPath("new_action_group"), actionGroup(hidden, reserved, "c", "d"))); - assertActionGroup(ok(() -> client.get(apiPath("new_action_group"))), "new_action_group", List.of("c", "d")); + // create + assertThat(client.putJson(apiPath("new_action_group"), actionGroup(hidden, reserved, "a", "b")), isCreated()); + var response = client.get(apiPath("new_action_group")); + assertThat(response, isOk()); + assertActionGroup(response, "new_action_group", List.of("a", "b")); - ok(() -> client.delete(apiPath("new_action_group"))); - notFound(() -> client.get(apiPath("new_action_group"))); - - ok(() -> client.patch(apiPath(), patch(addOp("new_action_group_for_patch", actionGroup(hidden, reserved, "e", "f"))))); - assertActionGroup(ok(() -> client.get(apiPath("new_action_group_for_patch"))), "new_action_group_for_patch", List.of("e", "f")); - - ok(() -> client.patch(apiPath("new_action_group_for_patch"), patch(replaceOp("allowed_actions", configJsonArray("g", "h"))))); - assertActionGroup(ok(() -> client.get(apiPath("new_action_group_for_patch"))), "new_action_group_for_patch", List.of("g", "h")); - - ok(() -> client.patch(apiPath(), patch(removeOp("new_action_group_for_patch")))); - notFound(() -> client.get(apiPath("new_action_group_for_patch"))); + // update + assertThat(client.putJson(apiPath("new_action_group"), actionGroup(hidden, reserved, "c", "d")), isOk()); + response = client.get(apiPath("new_action_group")); + assertThat(response, isOk()); + assertActionGroup(response, "new_action_group", List.of("c", "d")); + + // delete + assertThat(client.delete(apiPath("new_action_group")), isOk()); + response = client.get(apiPath("new_action_group")); + assertThat(response, isNotFound()); + + // patch add + assertThat(client.patch(apiPath(), patch(addOp("new_action_group_for_patch", actionGroup(hidden, reserved, "e", "f")))), isOk()); + response = client.get(apiPath("new_action_group_for_patch")); + assertThat(response, isOk()); + assertActionGroup(response, "new_action_group_for_patch", List.of("e", "f")); + + // patch replace + assertThat( + client.patch(apiPath("new_action_group_for_patch"), patch(replaceOp("allowed_actions", configJsonArray("g", "h")))), + isOk() + ); + response = client.get(apiPath("new_action_group_for_patch")); + assertThat(response, isOk()); + assertActionGroup(response, "new_action_group_for_patch", List.of("g", "h")); + + // patch remove + assertThat(client.patch(apiPath(), patch(removeOp("new_action_group_for_patch"))), isOk()); + response = client.get(apiPath("new_action_group_for_patch")); + assertThat(response, isNotFound()); } @Override void verifyBadRequestOperations(final TestRestClient client) throws Exception { // put - badRequest(() -> client.putJson(apiPath("some_action_group"), EMPTY_BODY)); - badRequestWithMessage( - () -> client.putJson(apiPath("kibana_user"), actionGroup("a", "b")), - "kibana_user is an existing role. A action group cannot be named with an existing role name." + assertThat(client.putJson(apiPath("some_action_group"), EMPTY_BODY), isBadRequest()); + assertThat( + client.putJson(apiPath("kibana_user"), actionGroup("a", "b")), + isBadRequest("/message", "kibana_user is an existing role. A action group cannot be named with an existing role name.") ); - badRequestWithMessage( - () -> client.putJson(apiPath("reference_itself"), actionGroup("reference_itself")), - "reference_itself cannot be an allowed_action of itself" + assertThat( + client.putJson(apiPath("reference_itself"), actionGroup("reference_itself")), + isBadRequest("/message", "reference_itself cannot be an allowed_action of itself") ); - - badRequestWithMessage(() -> client.putJson(apiPath("some_action_group"), (builder, params) -> { + assertThat(client.putJson(apiPath("some_action_group"), (builder, params) -> { builder.startObject().field("type", "asdasdsad").field("allowed_actions"); configJsonArray("g", "f").toXContent(builder, params); return builder.endObject(); - }), "Invalid action group type: asdasdsad. Supported types are: cluster, index."); + }), isBadRequest("/message", "Invalid action group type: asdasdsad. Supported types are: cluster, index.")); - assertMissingMandatoryKeys( - badRequest(() -> client.putJson(apiPath("some_action_group"), configJsonArray("a", "b", "c"))), - "allowed_actions" + assertThat( + client.putJson(apiPath("some_action_group"), configJsonArray("a", "b", "c")), + isBadRequest("/missing_mandatory_keys/keys", "allowed_actions") ); - assertMissingMandatoryKeys( - badRequest(() -> client.putJson(apiPath("some_action_group"), configJsonArray("a", "b", "c"))), - "allowed_actions" + // duplicate check retained from original + assertThat( + client.putJson(apiPath("some_action_group"), configJsonArray("a", "b", "c")), + isBadRequest("/missing_mandatory_keys/keys", "allowed_actions") ); final ToXContentObject unknownJsonFields = (builder, params) -> { @@ -186,68 +241,59 @@ void verifyBadRequestOperations(final TestRestClient client) throws Exception { configJsonArray("g", "h").toXContent(builder, params); return builder.endObject(); }; - assertInvalidKeys(badRequest(() -> client.putJson(apiPath("some_action_group"), unknownJsonFields)), "a,c"); + assertThat(client.putJson(apiPath("some_action_group"), unknownJsonFields), isBadRequest("/invalid_keys/keys", "a,c")); - assertNullValuesInArray(badRequest(() -> client.putJson(apiPath("some_action_group"), (builder, params) -> { + assertThat(client.putJson(apiPath("some_action_group"), (builder, params) -> { builder.startObject().field("type", randomType()).field("allowed_actions"); configJsonArray("g", null, "f").toXContent(builder, params); return builder.endObject(); - }))); - assertWrongDataType( - client.putJson( - apiPath("some_action_group"), - (builder, params) -> builder.startObject().field("allowed_actions", "a").endObject() - ), - Map.of("allowed_actions", "Array expected") - ); - // patch - badRequest(() -> client.patch(apiPath("some_action_group"), EMPTY_BODY)); - badRequest(() -> client.patch(apiPath(), patch(addOp("some_action_group", EMPTY_BODY)))); - badRequest(() -> client.patch(apiPath(), patch(replaceOp("some_action_group", EMPTY_BODY)))); + }), isBadRequest("/reason", "`null` or blank values are not allowed as json array elements")); - badRequestWithMessage( - () -> client.patch(apiPath(), patch(addOp("kibana_user", actionGroup("a")))), - "kibana_user is an existing role. A action group cannot be named with an existing role name." + // patch + assertThat(client.patch(apiPath("some_action_group"), EMPTY_BODY), isBadRequest()); + assertThat(client.patch(apiPath(), patch(addOp("some_action_group", EMPTY_BODY))), isBadRequest()); + assertThat(client.patch(apiPath(), patch(replaceOp("some_action_group", EMPTY_BODY))), isBadRequest()); + assertThat( + client.patch(apiPath(), patch(addOp("kibana_user", actionGroup("a")))), + isBadRequest("/message", "kibana_user is an existing role. A action group cannot be named with an existing role name.") ); - badRequestWithMessage( - () -> client.patch(apiPath(), patch(addOp("reference_itself", actionGroup("reference_itself")))), - "reference_itself cannot be an allowed_action of itself" + assertThat( + client.patch(apiPath(), patch(addOp("reference_itself", actionGroup("reference_itself")))), + isBadRequest("/message", "reference_itself cannot be an allowed_action of itself") ); - - assertMissingMandatoryKeys( - badRequest(() -> client.patch(apiPath(), patch(addOp("some_action_group", configJsonArray("a", "b", "c"))))), - "allowed_actions" + assertThat( + client.patch(apiPath(), patch(addOp("some_action_group", configJsonArray("a", "b", "c")))), + isBadRequest("/missing_mandatory_keys/keys", "allowed_actions") ); - badRequest(() -> client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, params) -> { + + assertThat(client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, params) -> { builder.startObject().field("type", "aaaa").field("allowed_actions"); configJsonArray("g", "f").toXContent(builder, params); return builder.endObject(); - })))); + }))), isBadRequest()); - badRequest(() -> client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, parameter) -> { + assertThat(client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, parameter) -> { builder.startObject(); unknownJsonFields.toXContent(builder, parameter); return builder.endObject(); - })))); - assertNullValuesInArray( - badRequest(() -> client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, params) -> { - builder.startObject().field("type", randomType()).field("allowed_actions"); - configJsonArray("g", null, "f").toXContent(builder, params); - return builder.endObject(); - })))) - ); - assertWrongDataType( - client.patch( - apiPath(), - patch( - addOp( - "some_action_group", - (ToXContentObject) (builder, params) -> builder.startObject().field("allowed_actions", "a").endObject() - ) + }))), isBadRequest()); + + assertThat(client.patch(apiPath(), patch(addOp("some_action_group", (ToXContentObject) (builder, params) -> { + builder.startObject().field("type", randomType()).field("allowed_actions"); + configJsonArray("g", null, "f").toXContent(builder, params); + return builder.endObject(); + }))), isBadRequest("/reason", "`null` or blank values are not allowed as json array elements")); + + var response = client.patch( + apiPath(), + patch( + addOp( + "some_action_group", + (ToXContentObject) (builder, params) -> builder.startObject().field("allowed_actions", "a").endObject() ) - ), - Map.of("allowed_actions", "Array expected") + ) ); + assertThat(response, isBadRequest().withAttribute("/status", "error").withAttribute("/allowed_actions", "Array expected")); } void assertActionGroup(final TestRestClient.HttpResponse response, final String actionGroupName, final List allowedActions) { diff --git a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java index 748b036d16..c1d6999fd8 100644 --- a/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/CertificatesRestApiIntegrationTest.java @@ -14,19 +14,19 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.StringJoiner; import java.util.stream.Collectors; import com.carrotsearch.randomizedtesting.RandomizedContext; import com.fasterxml.jackson.databind.JsonNode; +import org.junit.ClassRule; import org.junit.Test; -import org.opensearch.common.CheckedConsumer; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.security.ssl.config.CertType; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.certificate.TestCertificates; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.LocalOpenSearchCluster; import org.opensearch.test.framework.cluster.TestRestClient; @@ -36,6 +36,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; import static junit.framework.TestCase.fail; public class CertificatesRestApiIntegrationTest extends AbstractApiIntegrationTest { @@ -43,22 +45,18 @@ public class CertificatesRestApiIntegrationTest extends AbstractApiIntegrationTe final static String REGULAR_USER = "regular_user"; final static String ROOT_CA = "Root CA"; - static { - testSecurityConfig.roles( - new TestSecurityConfig.Role("simple_user_role").clusterPermissions("cluster:admin/security/certificates/info") + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting(SECURITY_RESTAPI_ADMIN_ENABLED, true) + .roles(new TestSecurityConfig.Role("simple_user_role").clusterPermissions("cluster:admin/security/certificates/info")) + .rolesMapping(new TestSecurityConfig.RoleMapping("simple_user_role").users(REGULAR_USER, ADMIN_USER.getName())) + .users( + new TestSecurityConfig.User(REGULAR_USER), + new TestSecurityConfig.User(REST_API_ADMIN_SSL_INFO).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.SSL, CERTS_INFO_ACTION)) + ) ) - .rolesMapping(new TestSecurityConfig.RoleMapping("simple_user_role").users(REGULAR_USER, ADMIN_USER_NAME)) - .user(new TestSecurityConfig.User(REGULAR_USER)) - .withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions()) - .withRestAdminUser(REST_API_ADMIN_SSL_INFO, restAdminPermission(Endpoint.SSL, CERTS_INFO_ACTION)); - } - - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(SECURITY_RESTAPI_ADMIN_ENABLED, true); - return clusterSettings; - } + .build(); @Override protected String apiPathPrefix() { @@ -78,59 +76,65 @@ protected String sslCertsPath(String... path) { @Test public void forbiddenForRegularUser() throws Exception { - withUser(REGULAR_USER, client -> forbidden(() -> client.get(sslCertsPath()))); + try (TestRestClient client = localCluster.getRestClient(REGULAR_USER, DEFAULT_PASSWORD)) { + assertThat(client.get(sslCertsPath()), isForbidden()); + } } @Test public void forbiddenForAdminUser() throws Exception { - withUser(ADMIN_USER_NAME, client -> forbidden(() -> client.get(sslCertsPath()))); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.get(sslCertsPath()), isForbidden()); + } } @Test public void availableForTlsAdmin() throws Exception { - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)) - ); + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + verifySSLCertsInfo(client, List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)); + } } @Test public void availableForRestAdmin() throws Exception { - withUser(REST_ADMIN_USER, verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT))); - withUser(REST_API_ADMIN_SSL_INFO, verifySSLCertsInfo(List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT))); + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + verifySSLCertsInfo(client, List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)); + } + try (TestRestClient client = localCluster.getRestClient(REST_API_ADMIN_SSL_INFO, DEFAULT_PASSWORD)) { + verifySSLCertsInfo(client, List.of(CertType.HTTP, CertType.TRANSPORT, CertType.TRANSPORT_CLIENT)); + } } @Test public void timeoutTest() throws Exception { - withUser(REST_ADMIN_USER, this::verifyTimeoutRequest); + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + verifyTimeoutRequest(client); + } } private void verifyTimeoutRequest(final TestRestClient client) throws Exception { - ok(() -> client.get(sslCertsPath() + "?timeout=0")); + assertThat(client.get(sslCertsPath() + "?timeout=0"), isOk()); } - private CheckedConsumer verifySSLCertsInfo(List expectCerts) { - return testRestClient -> { - try { - assertSSLCertsInfo(localCluster.nodes(), expectCerts, ok(() -> testRestClient.get(sslCertsPath()))); - if (localCluster.nodes().size() > 1) { - final var randomNodes = randomNodes(); - final var nodeIds = randomNodes.stream() - .map(n -> n.esNode().getNodeEnvironment().nodeId()) - .collect(Collectors.joining(",")); - assertSSLCertsInfo(randomNodes, expectCerts, ok(() -> testRestClient.get(sslCertsPath(nodeIds)))); - } - final var randomCertType = randomFrom(expectCerts); - assertSSLCertsInfo( - localCluster.nodes(), - List.of(randomCertType), - ok(() -> testRestClient.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType))) - ); - } catch (Exception e) { - fail("Verify SSLCerts info failed with exception: " + e.getMessage()); + private void verifySSLCertsInfo(final TestRestClient testRestClient, List expectCerts) { + try { + assertSSLCertsInfo(localCluster.nodes(), expectCerts, testRestClient.get(sslCertsPath())); + if (localCluster.nodes().size() > 1) { + final var randomNodes = randomNodes(); + final var nodeIds = randomNodes.stream() + .map(n -> n.esNode().getNodeEnvironment().nodeId()) + .collect(Collectors.joining(",")); + assertSSLCertsInfo(randomNodes, expectCerts, testRestClient.get(sslCertsPath(nodeIds))); } - }; + final var randomCertType = randomFrom(expectCerts); + assertSSLCertsInfo( + localCluster.nodes(), + List.of(randomCertType), + testRestClient.get(String.format("%s?cert_type=%s", sslCertsPath(), randomCertType)) + ); + } catch (Exception e) { + fail("Verify SSLCerts info failed with exception: " + e.getMessage()); + } } private void assertSSLCertsInfo( diff --git a/src/integrationTest/java/org/opensearch/security/api/ConfigRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/ConfigRestApiIntegrationTest.java index 16b089f99b..b69e271769 100644 --- a/src/integrationTest/java/org/opensearch/security/api/ConfigRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/ConfigRestApiIntegrationTest.java @@ -10,38 +10,48 @@ */ package org.opensearch.security.api; -import java.util.Map; import java.util.StringJoiner; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.api.Endpoint; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; +import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; import static org.opensearch.security.support.ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotAllowed; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class ConfigRestApiIntegrationTest extends AbstractApiIntegrationTest { final static String REST_API_ADMIN_CONFIG_UPDATE = "rest-api-admin-config-update"; - static { - testSecurityConfig.withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions()) - .withRestAdminUser(REST_API_ADMIN_CONFIG_UPDATE, restAdminPermission(Endpoint.CONFIG, SECURITY_CONFIG_UPDATE)); - } - - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true); - clusterSettings.put(SECURITY_RESTAPI_ADMIN_ENABLED, true); - return clusterSettings; - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting( + SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, + true + ) + .nodeSetting(SECURITY_RESTAPI_ADMIN_ENABLED, true) + .users( + new TestSecurityConfig.User(REST_API_ADMIN_CONFIG_UPDATE).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions( + restAdminPermission(Endpoint.CONFIG, SECURITY_CONFIG_UPDATE) + ) + ) + ) + .build(); private String securityConfigPath(final String... path) { final var fullPath = new StringJoiner("/").add(super.apiPath("securityconfig")); @@ -52,44 +62,51 @@ private String securityConfigPath(final String... path) { @Test public void forbiddenForRegularUsers() throws Exception { - withUser(NEW_USER, client -> { - forbidden(() -> client.get(securityConfigPath())); - forbidden(() -> client.putJson(securityConfigPath("config"), EMPTY_BODY)); - forbidden(() -> client.patch(securityConfigPath(), EMPTY_BODY)); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(securityConfigPath()), isForbidden()); + assertThat(client.putJson(securityConfigPath("config"), EMPTY_BODY), isForbidden()); + assertThat(client.patch(securityConfigPath(), EMPTY_BODY), isForbidden()); verifyNotAllowedMethods(client); - }); + } } @Test public void partiallyAvailableForAdminUser() throws Exception { - withUser(ADMIN_USER_NAME, client -> ok(() -> client.get(securityConfigPath()))); - withUser(ADMIN_USER_NAME, client -> { - badRequest(() -> client.putJson(securityConfigPath("xxx"), EMPTY_BODY)); - forbidden(() -> client.putJson(securityConfigPath("config"), EMPTY_BODY)); - forbidden(() -> client.patch(securityConfigPath(), EMPTY_BODY)); - }); - withUser(ADMIN_USER_NAME, this::verifyNotAllowedMethods); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.get(securityConfigPath()), isOk()); + assertThat(client.putJson(securityConfigPath("xxx"), EMPTY_BODY), isBadRequest()); + assertThat(client.putJson(securityConfigPath("config"), EMPTY_BODY), isForbidden()); + assertThat(client.patch(securityConfigPath(), EMPTY_BODY), isForbidden()); + verifyNotAllowedMethods(client); + } } @Test public void availableForTlsAdminUser() throws Exception { - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> ok(() -> client.get(securityConfigPath()))); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifyUpdate); + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.get(securityConfigPath()), isOk()); + verifyUpdate(client); + } } @Test public void availableForRestAdminUser() throws Exception { - withUser(REST_ADMIN_USER, client -> ok(() -> client.get(securityConfigPath()))); - withUser(REST_ADMIN_USER, this::verifyUpdate); - withUser(REST_API_ADMIN_CONFIG_UPDATE, this::verifyUpdate); + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + assertThat(client.get(securityConfigPath()), isOk()); + verifyUpdate(client); + } + try (TestRestClient client = localCluster.getRestClient(REST_API_ADMIN_CONFIG_UPDATE, DEFAULT_PASSWORD)) { + verifyUpdate(client); + } } void verifyUpdate(final TestRestClient client) throws Exception { - badRequest(() -> client.putJson(securityConfigPath("xxx"), EMPTY_BODY)); + assertThat(client.putJson(securityConfigPath("xxx"), EMPTY_BODY), isBadRequest()); verifyNotAllowedMethods(client); TestRestClient.HttpResponse resp = client.get(securityConfigPath()); - final var configJson = ok(() -> client.get(securityConfigPath())).bodyAsJsonNode(); + assertThat(resp, isOk()); + final var configJson = resp.bodyAsJsonNode(); final var authFailureListeners = DefaultObjectMapper.objectMapper.createObjectNode(); authFailureListeners.set( "ip_rate_limiting", @@ -114,20 +131,31 @@ void verifyUpdate(final TestRestClient client) throws Exception { ); final var dynamicConfigJson = (ObjectNode) configJson.get("config").get("dynamic"); dynamicConfigJson.set("auth_failure_listeners", authFailureListeners); - ok(() -> client.putJson(securityConfigPath("config"), DefaultObjectMapper.writeValueAsString(configJson.get("config"), false))); + assertThat( + client.putJson(securityConfigPath("config"), DefaultObjectMapper.writeValueAsString(configJson.get("config"), false)), + isOk() + ); String originalHostResolverMode = configJson.get("config").get("dynamic").get("hosts_resolver_mode").asText(); String nextOriginalHostResolverMode = originalHostResolverMode.equals("other") ? "ip-only" : "other"; - ok(() -> client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", nextOriginalHostResolverMode)))); - ok(() -> client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", originalHostResolverMode)))); - ok( - () -> client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", originalHostResolverMode))), - "No updates required" + assertThat( + client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", nextOriginalHostResolverMode))), + isOk() + ); + assertThat( + client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", originalHostResolverMode))), + isOk() + ); + TestRestClient.HttpResponse last = client.patch( + securityConfigPath(), + patch(replaceOp("/config/dynamic/hosts_resolver_mode", originalHostResolverMode)) ); + assertThat(last, isOk()); + assertResponseBody(last.getBody(), "No updates required"); } - void verifyNotAllowedMethods(final TestRestClient client) throws Exception { - methodNotAllowed(() -> client.postJson(securityConfigPath(), EMPTY_BODY)); - methodNotAllowed(() -> client.delete(securityConfigPath())); + void verifyNotAllowedMethods(final TestRestClient client) { + assertThat(client.postJson(securityConfigPath(), EMPTY_BODY), isNotAllowed()); + assertThat(client.delete(securityConfigPath()), isNotAllowed()); } } diff --git a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java index 635d9ecff4..d7cfe41311 100644 --- a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoTest.java @@ -13,10 +13,13 @@ import java.util.List; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -24,16 +27,16 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.rest.DashboardsInfoAction.DEFAULT_PASSWORD_MESSAGE; import static org.opensearch.security.rest.DashboardsInfoAction.DEFAULT_PASSWORD_REGEX; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class DashboardsInfoTest extends AbstractApiIntegrationTest { - static { - testSecurityConfig.user( - new TestSecurityConfig.User("dashboards_user").roles( - new Role("dashboards_role").indexPermissions("read").on("*").clusterPermissions("cluster_composite_ops") - ) - ); - } + static final TestSecurityConfig.User DASHBOARDS_USER = new TestSecurityConfig.User("dashboards_user").roles( + new Role("dashboards_role").indexPermissions("read").on("*").clusterPermissions("cluster_composite_ops") + ); + + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users(DASHBOARDS_USER).build(); private String apiPath() { return randomFrom(List.of(PLUGINS_PREFIX + "/dashboardsinfo", LEGACY_OPENDISTRO_PREFIX + "/kibanainfo")); @@ -41,10 +44,11 @@ private String apiPath() { @Test public void testDashboardsInfoValidationMessage() throws Exception { - withUser("dashboards_user", client -> { - final var response = ok(() -> client.get(apiPath())); + try (TestRestClient client = localCluster.getRestClient(DASHBOARDS_USER)) { + final var response = client.get(apiPath()); + assertThat(response, isOk()); assertThat(response.getTextFromJsonBody("/password_validation_error_message"), equalTo(DEFAULT_PASSWORD_MESSAGE)); assertThat(response.getTextFromJsonBody("/password_validation_regex"), equalTo(DEFAULT_PASSWORD_REGEX)); - }); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoWithSettingsTest.java b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoWithSettingsTest.java index af8eeb2c8a..8aba10fe35 100644 --- a/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoWithSettingsTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/DashboardsInfoWithSettingsTest.java @@ -12,18 +12,22 @@ package org.opensearch.security.api; import java.util.List; -import java.util.Map; +import org.junit.ClassRule; import org.junit.Test; -import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.TestSecurityConfig; import org.opensearch.test.framework.TestSecurityConfig.Role; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE; +import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class DashboardsInfoWithSettingsTest extends AbstractApiIntegrationTest { @@ -32,21 +36,18 @@ public class DashboardsInfoWithSettingsTest extends AbstractApiIntegrationTest { private static final String CUSTOM_PASSWORD_MESSAGE = "Password must be minimum 5 characters long and must contain at least one uppercase letter, one lowercase letter, one digit, and one special character."; - static { - testSecurityConfig.user( + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting( + SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, + CUSTOM_PASSWORD_REGEX + ) + .nodeSetting(SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, CUSTOM_PASSWORD_MESSAGE) + .users( new TestSecurityConfig.User("dashboards_user").roles( new Role("dashboards_role").indexPermissions("read").on("*").clusterPermissions("cluster_composite_ops") ) - ); - } - - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, CUSTOM_PASSWORD_REGEX); - clusterSettings.put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, CUSTOM_PASSWORD_MESSAGE); - return clusterSettings; - } + ) + .build(); private String apiPath() { return randomFrom(List.of(PLUGINS_PREFIX + "/dashboardsinfo", LEGACY_OPENDISTRO_PREFIX + "/kibanainfo")); @@ -55,10 +56,11 @@ private String apiPath() { @Test public void testDashboardsInfoValidationMessageWithCustomMessage() throws Exception { - withUser("dashboards_user", client -> { - final var response = ok(() -> client.get(apiPath())); + try (TestRestClient client = localCluster.getRestClient("dashboards_user", DEFAULT_PASSWORD)) { + final var response = client.get(apiPath()); + assertThat(response, isOk()); assertThat(response.getTextFromJsonBody("/password_validation_error_message"), equalTo(CUSTOM_PASSWORD_MESSAGE)); assertThat(response.getTextFromJsonBody("/password_validation_regex"), equalTo(CUSTOM_PASSWORD_REGEX)); - }); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/DefaultApiAvailabilityIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/DefaultApiAvailabilityIntegrationTest.java index 7ac2262899..568f7fea8d 100644 --- a/src/integrationTest/java/org/opensearch/security/api/DefaultApiAvailabilityIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/DefaultApiAvailabilityIntegrationTest.java @@ -11,66 +11,87 @@ package org.opensearch.security.api; +import org.junit.ClassRule; import org.junit.Test; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotAllowed; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class DefaultApiAvailabilityIntegrationTest extends AbstractApiIntegrationTest { + @ClassRule + public static LocalCluster localCluster = clusterBuilder().build(); + @Test public void nodesDnApiIsNotAvailableByDefault() throws Exception { - withUser(NEW_USER, this::verifyNodesDnApi); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifyNodesDnApi); - } - - private void verifyNodesDnApi(final TestRestClient client) throws Exception { - badRequest(() -> client.get(apiPath("nodesdn"))); - badRequest(() -> client.putJson(apiPath("nodesdn", "cluster_1"), EMPTY_BODY)); - badRequest(() -> client.delete(apiPath("nodesdn", "cluster_1"))); - badRequest(() -> client.patch(apiPath("nodesdn", "cluster_1"), EMPTY_BODY)); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(apiPath("nodesdn")), isBadRequest()); + assertThat(client.putJson(apiPath("nodesdn", "cluster_1"), EMPTY_BODY), isBadRequest()); + assertThat(client.delete(apiPath("nodesdn", "cluster_1")), isBadRequest()); + assertThat(client.patch(apiPath("nodesdn", "cluster_1"), EMPTY_BODY), isBadRequest()); + } + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.get(apiPath("nodesdn")), isBadRequest()); + assertThat(client.putJson(apiPath("nodesdn", "cluster_1"), EMPTY_BODY), isBadRequest()); + assertThat(client.delete(apiPath("nodesdn", "cluster_1")), isBadRequest()); + assertThat(client.patch(apiPath("nodesdn", "cluster_1"), EMPTY_BODY), isBadRequest()); + } } @Test public void securityConfigIsNotAvailableByDefault() throws Exception { - withUser(NEW_USER, client -> { - forbidden(() -> client.get(apiPath("securityconfig"))); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(apiPath("securityconfig")), isForbidden()); verifySecurityConfigApi(client); - }); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> { - ok(() -> client.get(apiPath("securityconfig"))); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.get(apiPath("securityconfig")), isOk()); verifySecurityConfigApi(client); - }); + } } private void verifySecurityConfigApi(final TestRestClient client) throws Exception { - methodNotAllowed(() -> client.putJson(apiPath("securityconfig"), EMPTY_BODY)); - methodNotAllowed(() -> client.postJson(apiPath("securityconfig"), EMPTY_BODY)); - methodNotAllowed(() -> client.delete(apiPath("securityconfig"))); - forbidden(() -> client.patch(apiPath("securityconfig"), patch(replaceOp("/a/b/c", "other")))); + assertThat(client.putJson(apiPath("securityconfig"), EMPTY_BODY), isNotAllowed()); + assertThat(client.postJson(apiPath("securityconfig"), EMPTY_BODY), isNotAllowed()); + assertThat(client.delete(apiPath("securityconfig")), isNotAllowed()); + assertThat(client.patch(apiPath("securityconfig"), patch(replaceOp("/a/b/c", "other"))), isForbidden()); } @Test public void securityHealth() throws Exception { - withUser(NEW_USER, client -> ok(() -> client.get(securityPath("health")))); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> ok(() -> client.get(securityPath("health")))); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(securityPath("health")), isOk()); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.get(securityPath("health")), isOk()); + } } @Test public void securityAuthInfo() throws Exception { - withUser(NEW_USER, this::verifyAuthInfoApi); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifyAuthInfoApi); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + verifyAuthInfoApi(client); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + verifyAuthInfoApi(client); + } } private void verifyAuthInfoApi(final TestRestClient client) throws Exception { final var verbose = randomBoolean(); final TestRestClient.HttpResponse response; - if (verbose) response = ok(() -> client.get(securityPath("authinfo?verbose=" + verbose))); - else response = ok(() -> client.get(securityPath("authinfo"))); + if (verbose) response = client.get(securityPath("authinfo?verbose=" + verbose)); + else response = client.get(securityPath("authinfo")); + assertThat(response, isOk()); final var body = response.bodyAsJsonNode(); assertThat(response.getBody(), body.has("user")); assertThat(response.getBody(), body.has("user_name")); @@ -94,14 +115,14 @@ private void verifyAuthInfoApi(final TestRestClient client) throws Exception { @Test public void reloadSSLCertsNotAvailable() throws Exception { - withUser(NEW_USER, client -> { - forbidden(() -> client.putJson(apiPath("ssl", "http", "reloadcerts"), EMPTY_BODY)); - forbidden(() -> client.putJson(apiPath("ssl", "transport", "reloadcerts"), EMPTY_BODY)); - }); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> { - badRequest(() -> client.putJson(apiPath("ssl", "http", "reloadcerts"), EMPTY_BODY)); - badRequest(() -> client.putJson(apiPath("ssl", "transport", "reloadcerts"), EMPTY_BODY)); - }); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.putJson(apiPath("ssl", "http", "reloadcerts"), EMPTY_BODY), isForbidden()); + assertThat(client.putJson(apiPath("ssl", "transport", "reloadcerts"), EMPTY_BODY), isForbidden()); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.putJson(apiPath("ssl", "http", "reloadcerts"), EMPTY_BODY), isBadRequest()); + assertThat(client.putJson(apiPath("ssl", "transport", "reloadcerts"), EMPTY_BODY), isBadRequest()); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/FlushCacheApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/FlushCacheApiIntegrationTest.java index 2879a43c93..58782b578d 100644 --- a/src/integrationTest/java/org/opensearch/security/api/FlushCacheApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/FlushCacheApiIntegrationTest.java @@ -11,15 +11,25 @@ package org.opensearch.security.api; +import org.junit.ClassRule; import org.junit.Test; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.dlic.rest.support.Utils.PLUGINS_PREFIX; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotAllowed; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class FlushCacheApiIntegrationTest extends AbstractApiIntegrationTest { private final static String TEST_USER = "testuser"; + @ClassRule + public static LocalCluster localCluster = clusterBuilder().build(); + private String cachePath() { return super.apiPath("cache"); } @@ -35,26 +45,30 @@ protected String apiPathPrefix() { @Test public void testFlushCache() throws Exception { - withUser(NEW_USER, client -> { - forbidden(() -> client.delete(cachePath())); - forbidden(() -> client.delete(cachePath(TEST_USER))); - }); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), client -> { - methodNotAllowed(() -> client.get(cachePath())); - methodNotAllowed(() -> client.postJson(cachePath(), EMPTY_BODY)); - methodNotAllowed(() -> client.putJson(cachePath(), EMPTY_BODY)); - final var deleteAllCacheResponse = ok(() -> client.delete(cachePath())); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.delete(cachePath()), isForbidden()); + assertThat(client.delete(cachePath(TEST_USER)), isForbidden()); + } + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.get(cachePath()), isNotAllowed()); + assertThat(client.postJson(cachePath(), EMPTY_BODY), isNotAllowed()); + assertThat(client.putJson(cachePath(), EMPTY_BODY), isNotAllowed()); + + final var deleteAllCacheResponse = client.delete(cachePath()); + assertThat(deleteAllCacheResponse, isOk()); assertThat( deleteAllCacheResponse.getBody(), deleteAllCacheResponse.getTextFromJsonBody("/message"), is("Cache flushed successfully.") ); - final var deleteUserCacheResponse = ok(() -> client.delete(cachePath(TEST_USER))); + + final var deleteUserCacheResponse = client.delete(cachePath(TEST_USER)); + assertThat(deleteUserCacheResponse, isOk()); assertThat( deleteUserCacheResponse.getBody(), deleteUserCacheResponse.getTextFromJsonBody("/message"), is("Cache invalidated for user: " + TEST_USER) ); - }); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/InternalUsersRegExpPasswordRulesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/InternalUsersRegExpPasswordRulesRestApiIntegrationTest.java index 684f30e60b..76dd413454 100644 --- a/src/integrationTest/java/org/opensearch/security/api/InternalUsersRegExpPasswordRulesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/InternalUsersRegExpPasswordRulesRestApiIntegrationTest.java @@ -11,37 +11,40 @@ package org.opensearch.security.api; -import java.util.Map; import java.util.StringJoiner; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.security.dlic.rest.validation.PasswordValidator; import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.api.PatchPayloadHelper.addOp; import static org.opensearch.security.api.PatchPayloadHelper.patch; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class InternalUsersRegExpPasswordRulesRestApiIntegrationTest extends AbstractApiIntegrationTest { final static String PASSWORD_VALIDATION_ERROR_MESSAGE = "xxxxxxxx"; - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, PASSWORD_VALIDATION_ERROR_MESSAGE); - clusterSettings.put( + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, + PASSWORD_VALIDATION_ERROR_MESSAGE + ) + .nodeSetting( ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, "(?=.*[A-Z])(?=.*[^a-zA-Z\\\\d])(?=.*[0-9])(?=.*[a-z]).{8,}" - ); - clusterSettings.put( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - PasswordValidator.ScoreStrength.FAIR.name() - ); - return clusterSettings; - } + ) + .nodeSetting(ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, PasswordValidator.ScoreStrength.FAIR.name()) + .build(); String internalUsers(String... path) { final var fullPath = new StringJoiner("/").add(super.apiPath("internalusers")); @@ -61,46 +64,42 @@ ToXContentObject internalUserWithPassword(final String password) { @Test public void canNotCreateUsersWithPassword() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { // validate short passwords - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword(""))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("123"))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("1234567"))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("1Aa%"))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("123456789"))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("a123456789"))); - badRequest(() -> client.putJson(internalUsers("tooshoort"), internalUserWithPassword("A123456789"))); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("123")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("1234567")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("1Aa%")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("123456789")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("a123456789")), isBadRequest()); + assertThat(client.putJson(internalUsers("tooshoort"), internalUserWithPassword("A123456789")), isBadRequest()); // validate that password same as user - badRequest(() -> client.putJson(internalUsers("$1aAAAAAAAAC"), internalUserWithPassword("$1aAAAAAAAAC"))); - badRequest(() -> client.putJson(internalUsers("$1aAAAAAAAac"), internalUserWithPassword("$1aAAAAAAAAC"))); - badRequestWithReason( - () -> client.patch( - internalUsers(), - patch( - addOp("testuser1", internalUserWithPassword("$aA123456789")), - addOp("testuser2", internalUserWithPassword("testpassword2")) - ) - ), - PASSWORD_VALIDATION_ERROR_MESSAGE + assertThat(client.putJson(internalUsers("$1aAAAAAAAAC"), internalUserWithPassword("$1aAAAAAAAAC")), isBadRequest()); + assertThat(client.putJson(internalUsers("$1aAAAAAAAac"), internalUserWithPassword("$1aAAAAAAAAC")), isBadRequest()); + final var r = client.patch( + internalUsers(), + patch( + addOp("testuser1", internalUserWithPassword("$aA123456789")), + addOp("testuser2", internalUserWithPassword("testpassword2")) + ) ); + assertThat(r, isBadRequest("/reason", PASSWORD_VALIDATION_ERROR_MESSAGE)); // validate similarity - badRequestWithReason( - () -> client.putJson(internalUsers("some_user_name"), internalUserWithPassword("H3235,cc,some_User_Name")), - RequestContentValidator.ValidationError.SIMILAR_PASSWORD.message() - ); - }); + final var r2 = client.putJson(internalUsers("some_user_name"), internalUserWithPassword("H3235,cc,some_User_Name")); + assertThat(r2, isBadRequest("/reason", RequestContentValidator.ValidationError.SIMILAR_PASSWORD.message())); + } } @Test public void canCreateUsersWithPassword() throws Exception { - withUser(ADMIN_USER_NAME, client -> { - created(() -> client.putJson(internalUsers("ok1"), internalUserWithPassword("$aA123456789"))); - created(() -> client.putJson(internalUsers("ok2"), internalUserWithPassword("$Aa123456789"))); - created(() -> client.putJson(internalUsers("ok3"), internalUserWithPassword("$1aAAAAAAAAA"))); - ok(() -> client.putJson(internalUsers("ok3"), internalUserWithPassword("$1aAAAAAAAAC"))); - ok(() -> client.patch(internalUsers(), patch(addOp("ok3", internalUserWithPassword("$1aAAAAAAAAB"))))); - ok(() -> client.putJson(internalUsers("ok1"), internalUserWithPassword("Admin_123"))); - }); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.putJson(internalUsers("ok1"), internalUserWithPassword("$aA123456789")), isCreated()); + assertThat(client.putJson(internalUsers("ok2"), internalUserWithPassword("$Aa123456789")), isCreated()); + assertThat(client.putJson(internalUsers("ok3"), internalUserWithPassword("$1aAAAAAAAAA")), isCreated()); + assertThat(client.putJson(internalUsers("ok3"), internalUserWithPassword("$1aAAAAAAAAC")), isOk()); + assertThat(client.patch(internalUsers(), patch(addOp("ok3", internalUserWithPassword("$1aAAAAAAAAB")))), isOk()); + assertThat(client.putJson(internalUsers("ok1"), internalUserWithPassword("Admin_123")), isOk()); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/InternalUsersRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/InternalUsersRestApiIntegrationTest.java index a87121297f..7cd184f590 100644 --- a/src/integrationTest/java/org/opensearch/security/api/InternalUsersRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/InternalUsersRestApiIntegrationTest.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.apache.http.HttpStatus; import org.junit.Assert; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.common.xcontent.XContentType; @@ -33,17 +34,23 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.opensearch.security.api.PatchPayloadHelper.addOp; import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotAllowed; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class InternalUsersRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { @@ -57,15 +64,20 @@ public class InternalUsersRestApiIntegrationTest extends AbstractConfigEntityApi private final static String SOME_ROLE = "some-role"; - static { - testSecurityConfig.withRestAdminUser(REST_API_ADMIN_INTERNAL_USERS_ONLY, restAdminPermission(Endpoint.INTERNALUSERS)) - .user(new TestSecurityConfig.User(SERVICE_ACCOUNT_USER).attr("service", "true").attr("enabled", "true")) - .roles( - new TestSecurityConfig.Role(HIDDEN_ROLE).hidden(true), - new TestSecurityConfig.Role(RESERVED_ROLE).reserved(true), - new TestSecurityConfig.Role(SOME_ROLE) - ); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users( + new TestSecurityConfig.User(SERVICE_ACCOUNT_USER).attr("service", "true").attr("enabled", "true"), + new TestSecurityConfig.User(REST_API_ADMIN_INTERNAL_USERS_ONLY).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.INTERNALUSERS)) + ) + ) + .roles( + new TestSecurityConfig.Role(HIDDEN_ROLE).hidden(true), + new TestSecurityConfig.Role(RESERVED_ROLE).reserved(true), + new TestSecurityConfig.Role(SOME_ROLE) + ) + .build(); public InternalUsersRestApiIntegrationTest() { super("internalusers", new TestDescriptor() { @@ -92,6 +104,26 @@ public Optional restAdminLimitedUser() { }); } + @Test + public void forbiddenForRegularUsers() throws Exception { + super.forbiddenForRegularUsers(localCluster); + } + + @Test + public void availableForAdminUser() throws Exception { + super.availableForAdminUser(localCluster); + } + + @Test + public void availableForTLSAdminUser() throws Exception { + super.availableForTLSAdminUser(localCluster); + } + + @Test + public void availableForRESTAdminUser() throws Exception { + super.availableForRESTAdminUser(localCluster); + } + static ToXContentObject internalUserWithPassword(final String password) { return internalUser(null, null, null, password, null, null, null); } @@ -193,60 +225,59 @@ static ToXContentObject serviceUser(final Boolean enabled, final String password @Override void verifyBadRequestOperations(TestRestClient client) throws Exception { // bad query string parameter name - badRequest(() -> client.get(apiPath() + "?aaaaa=bbbbb")); + assertThat(client.get(apiPath() + "?aaaaa=bbbbb"), isBadRequest()); final var predefinedUserName = randomAsciiAlphanumOfLength(4); - created( - () -> client.putJson( + assertThat( + client.putJson( apiPath(predefinedUserName), internalUser(randomAsciiAlphanumOfLength(10), configJsonArray(generateArrayValues(false)), null, null) - ) + ), + isCreated() ); invalidJson(client, predefinedUserName); } void invalidJson(final TestRestClient client, final String predefinedUserName) throws Exception { // put - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY)); - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + assertThat(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), isBadRequest()); + assertThat(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("backend_roles"); randomConfigArray(false).toXContent(builder, params); builder.field("backend_roles"); randomConfigArray(false).toXContent(builder, params); return builder.endObject(); - })); - assertInvalidKeys(badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + }), isBadRequest()); + HttpResponse response = client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("unknown_json_property"); configJsonArray("a", "b").toXContent(builder, params); builder.field("backend_roles"); randomConfigArray(false).toXContent(builder, params); return builder.endObject(); - })), "unknown_json_property"); - assertWrongDataType( - client.putJson( - apiPath(randomAsciiAlphanumOfLength(10)), - (builder, params) -> builder.startObject() - .field("password", configJsonArray("a", "b")) - .field("hash") - .nullValue() - .field("backend_roles", "c") - .field("attributes", "d") - .field("opendistro_security_roles", "e") - .endObject() - ), - Map.of( - "password", - "String expected", - "hash", - "String expected", - "backend_roles", - "Array expected", - "attributes", - "Object expected", - "opendistro_security_roles", - "Array expected" - ) + }); + assertThat(response, isBadRequest()); + assertInvalidKeys(response, "unknown_json_property"); + + response = client.putJson( + apiPath(randomAsciiAlphanumOfLength(10)), + (builder, params) -> builder.startObject() + .field("password", configJsonArray("a", "b")) + .field("hash") + .nullValue() + .field("backend_roles", "c") + .field("attributes", "d") + .field("opendistro_security_roles", "e") + .endObject() + ); + assertThat( + response, + isBadRequest().withAttribute("/status", "error") + .withAttribute("/password", "String expected") + .withAttribute("/hash", "String expected") + .withAttribute("/backend_roles", "Array expected") + .withAttribute("/attributes", "Object expected") + .withAttribute("/opendistro_security_roles", "Array expected") ); assertNullValuesInArray( client.putJson( @@ -261,21 +292,22 @@ void invalidJson(final TestRestClient client, final String predefinedUserName) t ) ); // patch - badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(10), EMPTY_BODY)))); - badRequest( - () -> client.patch( + assertThat(client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(10), EMPTY_BODY))), isBadRequest()); + assertThat( + client.patch( apiPath(predefinedUserName), patch(replaceOp(randomFrom(List.of("opendistro_security_roles", "backend_roles", "attributes")), EMPTY_BODY)) - ) + ), + isBadRequest() ); - badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), (ToXContentObject) (builder, params) -> { + assertThat(client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), (ToXContentObject) (builder, params) -> { builder.startObject(); builder.field("unknown_json_property"); configJsonArray("a", "b").toXContent(builder, params); builder.field("backend_roles"); randomConfigArray(false).toXContent(builder, params); return builder.endObject(); - })))); + }))), isBadRequest()); assertWrongDataType( client.patch( apiPath(), @@ -338,7 +370,7 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien randomAttributes(), randomSecurityRoles() ); - created(() -> client.putJson(apiPath(usernamePut), newUserJsonPut)); + assertThat(client.putJson(apiPath(usernamePut), newUserJsonPut), isCreated()); assertInternalUser( ok(() -> client.get(apiPath(usernamePut))).bodyAsJsonNode().get(usernamePut), hidden, @@ -353,15 +385,15 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien randomAttributes(), randomSecurityRoles() ); - ok(() -> client.putJson(apiPath(usernamePut), updatedUserJsonPut)); + assertThat(client.putJson(apiPath(usernamePut), updatedUserJsonPut), isOk()); assertInternalUser( ok(() -> client.get(apiPath(usernamePut))).bodyAsJsonNode().get(usernamePut), hidden, reserved, Strings.toString(XContentType.JSON, updatedUserJsonPut) ); - ok(() -> client.delete(apiPath(usernamePut))); - notFound(() -> client.get(apiPath(usernamePut))); + assertThat(client.delete(apiPath(usernamePut)), isOk()); + assertThat(client.get(apiPath(usernamePut)), isNotFound()); // patch // TODO related to issue #4426 final var usernamePatch = randomAsciiAlphanumOfLength(10); @@ -373,22 +405,24 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien (builder, params) -> builder.startObject().endObject(), configJsonArray() ); - ok(() -> client.patch(apiPath(), patch(addOp(usernamePatch, newUserJsonPatch)))); + assertThat(client.patch(apiPath(), patch(addOp(usernamePatch, newUserJsonPatch))), isOk()); assertInternalUser( ok(() -> client.get(apiPath(usernamePatch))).bodyAsJsonNode().get(usernamePatch), hidden, reserved, Strings.toString(XContentType.JSON, newUserJsonPatch) ); - ok(() -> client.patch(apiPath(usernamePatch), patch(replaceOp("backend_roles", configJsonArray("c", "d"))))); - ok( - () -> client.patch( + assertThat(client.patch(apiPath(usernamePatch), patch(replaceOp("backend_roles", configJsonArray("c", "d")))), isOk()); + assertThat( + client.patch( apiPath(usernamePatch), patch(addOp("attributes", (ToXContentObject) (builder, params) -> builder.startObject().field("a", "b").endObject())) - ) + ), + isOk() ); - ok( - () -> client.patch(apiPath(usernamePatch), patch(addOp("opendistro_security_roles", configJsonArray(RESERVED_ROLE, SOME_ROLE)))) + assertThat( + client.patch(apiPath(usernamePatch), patch(addOp("opendistro_security_roles", configJsonArray(RESERVED_ROLE, SOME_ROLE)))), + isOk() ); } @@ -432,56 +466,58 @@ String filterBy(final String value) { @Test public void filters() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { assertFilterByUsers(ok(() -> client.get(apiPath())), true, true); assertFilterByUsers(ok(() -> client.get(filterBy("any"))), true, true); assertFilterByUsers(ok(() -> client.get(filterBy("internal"))), false, true); assertFilterByUsers(ok(() -> client.get(filterBy("service"))), true, false); assertFilterByUsers(ok(() -> client.get(filterBy("something"))), true, true); - }); + } } void assertFilterByUsers(final HttpResponse response, final boolean hasServiceUser, final boolean hasInternalUser) { assertThat(response.getBody(), response.bodyAsJsonNode().has(SERVICE_ACCOUNT_USER), is(hasServiceUser)); - assertThat(response.getBody(), response.bodyAsJsonNode().has(NEW_USER), is(hasInternalUser)); + assertThat(response.getBody(), response.bodyAsJsonNode().has(NEW_USER.getName()), is(hasInternalUser)); } @Test public void verifyPOSTOnlyForAuthTokenEndpoint() throws Exception { - withUser(ADMIN_USER_NAME, client -> { - badRequest(() -> client.post(apiPath(ADMIN_USER_NAME, "authtoken"))); - ok(() -> client.post(apiPath(SERVICE_ACCOUNT_USER, "authtoken"))); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.post(apiPath(ADMIN_USER.getName(), "authtoken")), isBadRequest()); + assertThat(client.post(apiPath(SERVICE_ACCOUNT_USER, "authtoken")), isOk()); /* should be notImplement but the call doesn't reach {@link org.opensearch.security.dlic.rest.api.InternalUsersApiAction#withAuthTokenPath(RestRequest)} */ - methodNotAllowed(() -> client.post(apiPath("randomPath"))); - }); + assertThat(client.post(apiPath("randomPath")), isNotAllowed()); + } } @Test public void userApiWithDotsInName() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { for (final var dottedUserName : List.of(".my.dotuser0", ".my.dot.user0")) { - created( - () -> client.putJson( + assertThat( + client.putJson( apiPath(dottedUserName), (builder, params) -> builder.startObject().field("password", randomAsciiAlphanumOfLength(10)).endObject() - ) + ), + isCreated() ); } for (final var dottedUserName : List.of(".my.dotuser1", ".my.dot.user1")) { - created( - () -> client.putJson( + assertThat( + client.putJson( apiPath(dottedUserName), (builder, params) -> builder.startObject() .field("hash", passwordHasher.hash(randomAsciiAlphanumOfLength(10).toCharArray())) .endObject() - ) + ), + isCreated() ); } for (final var dottedUserName : List.of(".my.dotuser2", ".my.dot.user2")) { - ok( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( addOp( @@ -491,12 +527,13 @@ public void userApiWithDotsInName() throws Exception { .endObject() ) ) - ) + ), + isOk() ); } for (final var dottedUserName : List.of(".my.dotuser3", ".my.dot.user3")) { - ok( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( addOp( @@ -506,91 +543,98 @@ public void userApiWithDotsInName() throws Exception { .endObject() ) ) - ) + ), + isOk() ); } - }); + } } @Test public void noPasswordChange() throws Exception { - withUser(ADMIN_USER_NAME, client -> { - created( - () -> client.putJson( + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat( + client.putJson( apiPath("user1"), (builder, params) -> builder.startObject() .field("hash", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m") .endObject() - ) + ), + isCreated() ); - badRequest( - () -> client.putJson( + assertThat( + client.putJson( apiPath("user1"), (builder, params) -> builder.startObject() .field("hash", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m") .field("password", "") .field("backend_roles", configJsonArray("admin", "role_a")) .endObject() - ) + ), + isBadRequest() ); - ok( - () -> client.putJson( + assertThat( + client.putJson( apiPath("user1"), (builder, params) -> builder.startObject() .field("hash", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m") .field("password", randomAsciiAlphanumOfLength(10)) .field("backend_roles", configJsonArray("admin", "role_a")) .endObject() - ) + ), + isOk() ); - created( - () -> client.putJson( + assertThat( + client.putJson( apiPath("user2"), (builder, params) -> builder.startObject() .field("hash", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m") .field("password", randomAsciiAlphanumOfLength(10)) .endObject() - ) + ), + isCreated() ); - badRequest( - () -> client.putJson( + assertThat( + client.putJson( apiPath("user2"), (builder, params) -> builder.startObject() .field("password", "") .field("backend_roles", configJsonArray("admin", "role_b")) .endObject() - ) + ), + isBadRequest() ); - ok( - () -> client.putJson( + assertThat( + client.putJson( apiPath("user2"), (builder, params) -> builder.startObject() .field("password", randomAsciiAlphanumOfLength(10)) .field("backend_roles", configJsonArray("admin", "role_b")) .endObject() - ) + ), + isOk() ); - }); + } } @Test public void securityRoles() throws Exception { final var userWithSecurityRoles = randomAsciiAlphanumOfLength(15); final var userWithSecurityRolesPassword = randomAsciiAlphanumOfLength(10); - withUser( - ADMIN_USER_NAME, - client -> ok( - () -> client.patch( - apiPath(), - patch(addOp(userWithSecurityRoles, internalUser(userWithSecurityRolesPassword, null, null, null))) - ) - ) - ); - withUser(userWithSecurityRoles, userWithSecurityRolesPassword, client -> forbidden(() -> client.get(apiPath()))); - withUser( - ADMIN_USER_NAME, - client -> ok( - () -> client.patch( + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat( + client.patch(apiPath(), patch(addOp(userWithSecurityRoles, internalUser(userWithSecurityRolesPassword, null, null, null)))), + isOk() + ); + } + + try (TestRestClient client = localCluster.getRestClient(userWithSecurityRoles, userWithSecurityRolesPassword)) { + assertThat(client.get(apiPath()), isForbidden()); + } + + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat( + client.patch( apiPath(), patch( replaceOp( @@ -603,38 +647,48 @@ public void securityRoles() throws Exception { ) ) ) - ) - ) - ); - withUser(userWithSecurityRoles, userWithSecurityRolesPassword, client -> ok(() -> client.get(apiPath()))); - withUser(ADMIN_USER_NAME, client -> impossibleToSetHiddenRoleIsNotAllowed(userWithSecurityRoles, client)); - withUser(ADMIN_USER_NAME, client -> settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client)); - - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client) - ); - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::canAssignedHiddenRole); + ), + isOk() + ); + } + + try (TestRestClient client = localCluster.getRestClient(userWithSecurityRoles, userWithSecurityRolesPassword)) { + assertThat(client.get(apiPath()), isOk()); + } + + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + impossibleToSetHiddenRoleIsNotAllowed(userWithSecurityRoles, client); + settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client); + } + + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client); + canAssignedHiddenRole(client); + } + + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client); + canAssignedHiddenRole(client); + } - for (final var restAdminUser : List.of(REST_ADMIN_USER, REST_API_ADMIN_INTERNAL_USERS_ONLY)) { - withUser(restAdminUser, client -> settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client)); - withUser(restAdminUser, localCluster.getAdminCertificate(), this::canAssignedHiddenRole); + try (TestRestClient client = localCluster.getRestClient(REST_API_ADMIN_INTERNAL_USERS_ONLY, DEFAULT_PASSWORD)) { + settingOfUnknownRoleIsNotAllowed(userWithSecurityRoles, client); + canAssignedHiddenRole(client); } } void impossibleToSetHiddenRoleIsNotAllowed(final String predefinedUserName, final TestRestClient client) throws Exception { // put - notFound( - () -> client.putJson( + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(10)), internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray(HIDDEN_ROLE)) ), - "Resource 'hidden-role' is not available." + isNotFound().withAttribute("/message", "Resource 'hidden-role' is not available.") ); // patch - notFound( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( addOp( @@ -642,35 +696,34 @@ void impossibleToSetHiddenRoleIsNotAllowed(final String predefinedUserName, fina internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray(HIDDEN_ROLE)) ) ) - ) + ), + isNotFound() ); // TODO related to issue #4426 - notFound( - () -> client.patch(apiPath(predefinedUserName), patch(addOp("opendistro_security_roles", configJsonArray(HIDDEN_ROLE)))) - + assertThat( + client.patch(apiPath(predefinedUserName), patch(addOp("opendistro_security_roles", configJsonArray(HIDDEN_ROLE)))), + isNotFound() ); } void canAssignedHiddenRole(final TestRestClient client) throws Exception { final var userNamePut = randomAsciiAlphanumOfLength(4); - created( - () -> client.putJson( - apiPath(userNamePut), - internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray(HIDDEN_ROLE)) - ) + assertThat( + client.putJson(apiPath(userNamePut), internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray(HIDDEN_ROLE))), + isCreated() ); } void settingOfUnknownRoleIsNotAllowed(final String predefinedUserName, final TestRestClient client) throws Exception { - notFound( - () -> client.putJson( + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(10)), internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray("unknown-role")) ), - "role 'unknown-role' not found." + isNotFound().withAttribute("/message", "role 'unknown-role' not found.") ); - notFound( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( addOp( @@ -678,16 +731,18 @@ void settingOfUnknownRoleIsNotAllowed(final String predefinedUserName, final Tes internalUser(randomAsciiAlphanumOfLength(10), null, null, configJsonArray("unknown-role")) ) ) - ) + ), + isNotFound() ); - notFound( - () -> client.patch(apiPath(predefinedUserName), patch(addOp("opendistro_security_roles", configJsonArray("unknown-role")))) + assertThat( + client.patch(apiPath(predefinedUserName), patch(addOp("opendistro_security_roles", configJsonArray("unknown-role")))), + isNotFound() ); } @Test public void parallelPutRequests() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { final var userName = randomAsciiAlphanumOfLength(10); final var httpResponses = new HttpResponse[10]; @@ -722,40 +777,41 @@ public void parallelPutRequests() throws Exception { break; } } - }); + } } @Test public void restrictedUsernameContents() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { { for (final var restrictedTerm : RESTRICTED_FROM_USERNAME) { for (final var username : List.of( randomAsciiAlphanumOfLength(2) + restrictedTerm + randomAsciiAlphanumOfLength(3), URLEncoder.encode(randomAsciiAlphanumOfLength(4) + ":" + randomAsciiAlphanumOfLength(3), StandardCharsets.UTF_8) )) { - final var putResponse = badRequest( - () -> client.putJson(apiPath(username), internalUserWithPassword(randomAsciiAlphanumOfLength(10))) + assertThat( + client.putJson(apiPath(username), internalUserWithPassword(randomAsciiAlphanumOfLength(10))), + isBadRequest("/message", restrictedTerm) ); - assertThat(putResponse.getBody(), containsString(restrictedTerm)); - final var patchResponse = badRequest( - () -> client.patch(apiPath(), patch(addOp(username, internalUserWithPassword(randomAsciiAlphanumOfLength(10))))) + assertThat( + client.patch(apiPath(), patch(addOp(username, internalUserWithPassword(randomAsciiAlphanumOfLength(10))))), + isBadRequest("/message", restrictedTerm) ); - assertThat(patchResponse.getBody(), containsString(restrictedTerm)); } } } - }); + } } @Test public void serviceUsers() throws Exception { - withUser(ADMIN_USER_NAME, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { // Add enabled service account then get it // TODO related to issue #4426 add default behave when enabled is true final var happyServiceLiveUserName = randomAsciiAlphanumOfLength(10); - created(() -> client.putJson(apiPath(happyServiceLiveUserName), serviceUser(true))); - final var serviceLiveResponse = ok(() -> client.get(apiPath(happyServiceLiveUserName))); + assertThat(client.putJson(apiPath(happyServiceLiveUserName), serviceUser(true)), isCreated()); + final var serviceLiveResponse = client.get(apiPath(happyServiceLiveUserName)); + assertThat(serviceLiveResponse, isOk()); assertThat( serviceLiveResponse.getBody(), serviceLiveResponse.getBooleanFromJsonBody("/" + happyServiceLiveUserName + "/attributes/service") @@ -767,8 +823,9 @@ public void serviceUsers() throws Exception { // Add disabled service account final var happyServiceDeadUserName = randomAsciiAlphanumOfLength(10); - created(() -> client.putJson(apiPath(happyServiceDeadUserName), serviceUser(false))); - final var serviceDeadResponse = ok(() -> client.get(apiPath(happyServiceDeadUserName))); + assertThat(client.putJson(apiPath(happyServiceDeadUserName), serviceUser(false)), isCreated()); + final var serviceDeadResponse = client.get(apiPath(happyServiceDeadUserName)); + assertThat(serviceDeadResponse, isOk()); assertThat( serviceDeadResponse.getBody(), serviceDeadResponse.getBooleanFromJsonBody("/" + happyServiceDeadUserName + "/attributes/service") @@ -778,27 +835,27 @@ public void serviceUsers() throws Exception { not(serviceDeadResponse.getBooleanFromJsonBody("/" + happyServiceDeadUserName + "/attributes/enabled")) ); // Add service account with password -- Should Fail - badRequest( - () -> client.putJson( - apiPath(randomAsciiAlphanumOfLength(10)), - serviceUserWithPassword(true, randomAsciiAlphanumOfLength(10)) - ) + assertThat( + client.putJson(apiPath(randomAsciiAlphanumOfLength(10)), serviceUserWithPassword(true, randomAsciiAlphanumOfLength(10))), + isBadRequest() ); // Add service with hash -- should fail - badRequest( - () -> client.putJson( + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(10)), serviceUserWithHash(true, passwordHasher.hash(randomAsciiAlphanumOfLength(10).toCharArray())) - ) + ), + isBadRequest() ); // Add Service account with password & Hash -- should fail final var password = randomAsciiAlphanumOfLength(10); - badRequest( - () -> client.putJson( + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(10)), serviceUser(true, password, passwordHasher.hash(password.toCharArray())) - ) + ), + isBadRequest() ); - }); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/InternalUsersScoreBasedPasswordRulesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/InternalUsersScoreBasedPasswordRulesRestApiIntegrationTest.java index b18a0c6fd6..8939b24301 100644 --- a/src/integrationTest/java/org/opensearch/security/api/InternalUsersScoreBasedPasswordRulesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/InternalUsersScoreBasedPasswordRulesRestApiIntegrationTest.java @@ -11,26 +11,28 @@ package org.opensearch.security.api; -import java.util.Map; import java.util.StringJoiner; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.security.dlic.rest.validation.RequestContentValidator; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.test.framework.cluster.LocalCluster; +import org.opensearch.test.framework.cluster.TestRestClient; +import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.api.PatchPayloadHelper.addOp; import static org.opensearch.security.api.PatchPayloadHelper.patch; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class InternalUsersScoreBasedPasswordRulesRestApiIntegrationTest extends AbstractApiIntegrationTest { - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 9); - return clusterSettings; - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, 9).build(); String internalUsers(String... path) { final var fullPath = new StringJoiner("/").add(super.apiPath("internalusers")); @@ -50,38 +52,45 @@ ToXContentObject internalUserWithPassword(final String password) { @Test public void canNotCreateUsersWithPassword() throws Exception { - withUser(ADMIN_USER_NAME, client -> { - badRequestWithReason( - () -> client.putJson(internalUsers("admin"), internalUserWithPassword("password89")), - RequestContentValidator.ValidationError.WEAK_PASSWORD.message() + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + final var r1 = client.putJson(internalUsers("admin"), internalUserWithPassword("password89")); + assertThat(r1, isBadRequest()); + assertThat( + r1.getTextFromJsonBody("/reason"), + org.hamcrest.Matchers.containsString(RequestContentValidator.ValidationError.WEAK_PASSWORD.message()) ); - badRequestWithReason( - () -> client.putJson(internalUsers("admin"), internalUserWithPassword("A123456789")), - RequestContentValidator.ValidationError.WEAK_PASSWORD.message() + + final var r2 = client.putJson(internalUsers("admin"), internalUserWithPassword("A123456789")); + assertThat(r2, isBadRequest()); + assertThat( + r2.getTextFromJsonBody("/reason"), + org.hamcrest.Matchers.containsString(RequestContentValidator.ValidationError.WEAK_PASSWORD.message()) ); - badRequestWithReason( - () -> client.putJson(internalUsers("admin"), internalUserWithPassword(randomAsciiAlphanumOfLengthBetween(2, 8))), - RequestContentValidator.ValidationError.INVALID_PASSWORD_TOO_SHORT.message() + + final var r3 = client.putJson(internalUsers("admin"), internalUserWithPassword(randomAsciiAlphanumOfLengthBetween(2, 8))); + assertThat(r3, isBadRequest()); + assertThat( + r3.getTextFromJsonBody("/reason"), + org.hamcrest.Matchers.containsString(RequestContentValidator.ValidationError.INVALID_PASSWORD_TOO_SHORT.message()) ); - }); + } } @Test public void canCreateUserWithPassword() throws Exception { - withUser(ADMIN_USER_NAME, client -> { - created( - () -> client.putJson( - internalUsers(randomAsciiAlphanumOfLength(10)), - internalUserWithPassword(randomAsciiAlphanumOfLength(9)) - ) + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + final var createdResp = client.putJson( + internalUsers(randomAsciiAlphanumOfLength(10)), + internalUserWithPassword(randomAsciiAlphanumOfLength(9)) ); - ok( - () -> client.patch( - internalUsers(), - patch(addOp(randomAsciiAlphanumOfLength(10), internalUserWithPassword(randomAsciiAlphanumOfLength(9)))) - ) + assertThat(createdResp, isCreated()); + + final var patchResp = client.patch( + internalUsers(), + patch(addOp(randomAsciiAlphanumOfLength(10), internalUserWithPassword(randomAsciiAlphanumOfLength(9)))) ); - }); + assertThat(patchResp, isOk()); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java index 7255007271..7cb08d645a 100644 --- a/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java @@ -13,11 +13,12 @@ import java.io.IOException; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.StringJoiner; import com.fasterxml.jackson.databind.JsonNode; +import org.junit.ClassRule; +import org.junit.Test; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.common.Strings; @@ -25,7 +26,9 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; +import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse; import com.nimbusds.jose.util.Pair; @@ -36,23 +39,33 @@ import static org.opensearch.security.api.PatchPayloadHelper.removeOp; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; import static org.opensearch.test.framework.TestSecurityConfig.Role; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class RolesMappingRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { - final static String REST_API_ADMIN_ROLES_MAPPING_ONLY = "rest-api-admin-roles-mapping-only"; + final static TestSecurityConfig.User REST_API_ADMIN_ROLES_MAPPING_ONLY = new TestSecurityConfig.User( + "rest-api-admin-roles-mapping-only" + ).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.ROLESMAPPING)) + ); final static String REST_ADMIN_ROLE = "rest-admin-role"; final static String REST_ADMIN_ROLE_WITH_MAPPING = "rest-admin-role-with-mapping"; - static { - testSecurityConfig.withRestAdminUser(REST_API_ADMIN_ROLES_MAPPING_ONLY, restAdminPermission(Endpoint.ROLESMAPPING)) - .roles( - new Role(REST_ADMIN_ROLE).reserved(true).clusterPermissions(allRestAdminPermissions()), - new Role(REST_ADMIN_ROLE_WITH_MAPPING).clusterPermissions(allRestAdminPermissions()) - ) - .rolesMapping(new TestSecurityConfig.RoleMapping(REST_ADMIN_ROLE_WITH_MAPPING)); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users(REST_API_ADMIN_ROLES_MAPPING_ONLY) + .roles( + new Role(REST_ADMIN_ROLE).reserved(true).clusterPermissions(allRestAdminPermissions()), + new Role(REST_ADMIN_ROLE_WITH_MAPPING).clusterPermissions(allRestAdminPermissions()) + ) + .rolesMapping(new TestSecurityConfig.RoleMapping(REST_ADMIN_ROLE_WITH_MAPPING)) + .build(); public RolesMappingRestApiIntegrationTest() { super("rolesmapping", new TestDescriptor() { @@ -81,11 +94,31 @@ public ToXContentObject jsonPropertyPayload() { @Override public Optional restAdminLimitedUser() { - return Optional.of(REST_API_ADMIN_ROLES_MAPPING_ONLY); + return Optional.of(REST_API_ADMIN_ROLES_MAPPING_ONLY.getName()); } }); } + @Test + public void forbiddenForRegularUsers() throws Exception { + super.forbiddenForRegularUsers(localCluster); + } + + @Test + public void availableForAdminUser() throws Exception { + super.availableForAdminUser(localCluster); + } + + @Test + public void availableForTLSAdminUser() throws Exception { + super.availableForTLSAdminUser(localCluster); + } + + @Test + public void availableForRESTAdminUser() throws Exception { + super.availableForRESTAdminUser(localCluster); + } + static ToXContentObject roleMappingWithUsers(ToXContentObject users) { return roleMapping(null, null, null, null, null, users, null); } @@ -168,52 +201,45 @@ String rolesApiPath(final String roleName) { } @Override - Pair predefinedHiddenAndReservedConfigEntities() throws Exception { + Pair predefinedHiddenAndReservedConfigEntities(LocalCluster localCluster) throws Exception { final var hiddenEntityName = randomAsciiAlphanumOfLength(10); final var reservedEntityName = randomAsciiAlphanumOfLength(10); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created(() -> client.putJson(rolesApiPath(hiddenEntityName), roleJson(true, null))) - ); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created( - () -> client.putJson( + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + assertThat(client.putJson(rolesApiPath(hiddenEntityName), roleJson(true, null)), isCreated()); + assertThat( + client.putJson( apiPath(hiddenEntityName), roleMapping(true, null, null, configJsonArray("a", "b"), configJsonArray(), configJsonArray(), configJsonArray()) - ) - ) - ); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created(() -> client.putJson(rolesApiPath(reservedEntityName), roleJson(null, true))) - ); - withUser( - ADMIN_USER_NAME, - localCluster.getAdminCertificate(), - client -> created( - () -> client.putJson( + ), + isCreated() + ); + assertThat(client.putJson(rolesApiPath(reservedEntityName), roleJson(null, true)), isCreated()); + assertThat( + client.putJson( apiPath(reservedEntityName), roleMapping(null, true, null, configJsonArray("a", "b"), configJsonArray(), configJsonArray(), configJsonArray()) - ) - ) - ); + ), + isCreated() + ); + + } + return Pair.of(hiddenEntityName, reservedEntityName); } @Override void creationOfReadOnlyEntityForbidden(String entityName, TestRestClient client, ToXContentObject... entities) throws Exception { - withUser(ADMIN_USER_NAME, adminClient -> created(() -> adminClient.putJson(rolesApiPath(entityName), roleJson()))); + try (TestRestClient adminClient = localCluster.getRestClient(ADMIN_USER)) { + assertThat(adminClient.putJson(rolesApiPath(entityName), roleJson()), isCreated()); + } + super.creationOfReadOnlyEntityForbidden(entityName, client, entities); } @Override void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient client) throws Exception { final String roleName = randomAsciiAlphanumOfLength(10); - created(() -> client.putJson(rolesApiPath(roleName), roleJson())); + assertThat(client.putJson(rolesApiPath(roleName), roleJson()), isCreated()); // put final var newPutRoleMappingJson = roleMapping( hidden, @@ -223,7 +249,7 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien randomArray(false), randomArray(false) ); - created(() -> client.putJson(apiPath(roleName), newPutRoleMappingJson)); + assertThat(client.putJson(apiPath(roleName), newPutRoleMappingJson), isCreated()); assertRoleMapping( ok(() -> client.get(apiPath(roleName))).bodyAsJsonNode().get(roleName), hidden, @@ -238,7 +264,7 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien randomArray(false), randomArray(false) ); - ok(() -> client.putJson(apiPath(roleName), updatePutRoleMappingJson)); + assertThat(client.putJson(apiPath(roleName), updatePutRoleMappingJson), isOk()); assertRoleMapping( ok(() -> client.get(apiPath(roleName))).bodyAsJsonNode().get(roleName), hidden, @@ -246,8 +272,8 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien Strings.toString(XContentType.JSON, updatePutRoleMappingJson) ); - ok(() -> client.delete(apiPath(roleName))); - notFound(() -> client.get(apiPath(roleName))); + assertThat(client.delete(apiPath(roleName)), isOk()); + assertThat(client.get(apiPath(roleName)), isNotFound()); // patch // TODO related to issue #4426 final var newPatchRoleMappingJson = roleMapping( @@ -258,22 +284,23 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien configJsonArray(), configJsonArray() ); - ok(() -> client.patch(apiPath(), patch(addOp(roleName, newPatchRoleMappingJson)))); + assertThat(client.patch(apiPath(), patch(addOp(roleName, newPatchRoleMappingJson))), isOk()); assertRoleMapping( ok(() -> client.get(apiPath(roleName))).bodyAsJsonNode().get(roleName), hidden, reserved, Strings.toString(XContentType.JSON, newPatchRoleMappingJson) ); - ok(() -> client.patch(apiPath(roleName), patch(replaceOp("backend_roles", configJsonArray("c", "d"))))); - ok(() -> client.patch(apiPath(roleName), patch(addOp("hosts", configJsonArray("e", "f"))))); - ok(() -> client.patch(apiPath(roleName), patch(addOp("users", configJsonArray("g", "h"))))); - ok(() -> client.patch(apiPath(roleName), patch(addOp("and_backend_roles", configJsonArray("i", "j"))))); - ok(() -> client.patch(apiPath(roleName), patch(addOp("and_backend_roles", configJsonArray("i", "j")))), "No updates required"); - badRequest(() -> client.patch(apiPath(roleName), patch(replaceOp("backend_roles", configJsonArray("c", ""))))); - - ok(() -> client.patch(apiPath(), patch(removeOp(roleName)))); - notFound(() -> client.get(apiPath(roleName))); + assertThat(client.patch(apiPath(roleName), patch(replaceOp("backend_roles", configJsonArray("c", "d")))), isOk()); + assertThat(client.patch(apiPath(roleName), patch(addOp("hosts", configJsonArray("e", "f")))), isOk()); + assertThat(client.patch(apiPath(roleName), patch(addOp("users", configJsonArray("g", "h")))), isOk()); + assertThat(client.patch(apiPath(roleName), patch(addOp("and_backend_roles", configJsonArray("i", "j")))), isOk()); + // second identical update should still be OK; message assertion omitted + assertThat(client.patch(apiPath(roleName), patch(addOp("and_backend_roles", configJsonArray("i", "j")))), isOk()); + assertThat(client.patch(apiPath(roleName), patch(replaceOp("backend_roles", configJsonArray("c", "")))), isBadRequest()); + + assertThat(client.patch(apiPath(), patch(removeOp(roleName))), isOk()); + assertThat(client.get(apiPath(roleName)), isNotFound()); } void assertRoleMapping(final JsonNode actualObjectNode, final Boolean hidden, final Boolean reserved, final String expectedRoleJson) @@ -305,38 +332,36 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { return builder.endObject(); }; - notFound( - () -> client.putJson( + assertThat( + client.putJson( apiPath("unknown_role"), roleMapping(configJsonArray(), configJsonArray(), configJsonArray(), configJsonArray()) ), - "role 'unknown_role' not found." + isNotFound().withAttribute("/message", "role 'unknown_role' not found.") ); // put - badRequestWithReason( - () -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), - "Request body required for this action." + assertThat( + client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), + isBadRequest().withAttribute("/reason", "Request body required for this action.") ); - badRequestWithReason( - () -> client.putJson( + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> builder.startObject().field("users", configJsonArray()).field("users", configJsonArray()).endObject() ), - "Could not parse content of request." - ); - assertInvalidKeys( - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), unparseableJsonRequest)), - "unknown_json_property" + isBadRequest().withAttribute("/reason", "Could not parse content of request.") ); + HttpResponse response = client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), unparseableJsonRequest); + assertThat(response, isBadRequest()); + assertInvalidKeys(response, "unknown_json_property"); final var randomPropertyForPut = randomJsonProperty(); - assertWrongDataType( - client.putJson( - apiPath(randomAsciiAlphanumOfLength(5)), - (builder, params) -> builder.startObject().field(randomPropertyForPut).value("something").endObject() - ), - Map.of(randomPropertyForPut, "Array expected") + + response = client.putJson( + apiPath(randomAsciiAlphanumOfLength(5)), + (builder, params) -> builder.startObject().field(randomPropertyForPut).value("something").endObject() ); + assertThat(response, isBadRequest().withAttribute("/status", "error").withAttribute("/" + randomPropertyForPut, "Array expected")); assertNullValuesInArray( client.putJson( apiPath(randomAsciiAlphanumOfLength(5)), @@ -350,52 +375,49 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { ); // patch final var predefinedRole = randomAsciiAlphanumOfLength(5); - created(() -> client.putJson(rolesApiPath(predefinedRole), roleJson())); - created( - () -> client.putJson( + assertThat(client.putJson(rolesApiPath(predefinedRole), roleJson()), isCreated()); + assertThat( + client.putJson( apiPath(predefinedRole), roleMapping(configJsonArray("a", "b"), configJsonArray(), configJsonArray(), configJsonArray()) - ) + ), + isCreated() ); - badRequest(() -> client.patch(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY)); - badRequest( - () -> client.patch( + assertThat(client.patch(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), isBadRequest()); + assertThat( + client.patch( apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> builder.startObject().field("users", configJsonArray()).field("users", configJsonArray()).endObject() - ) - ); - assertInvalidKeys( - badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), unparseableJsonRequest)))), - "unknown_json_property" + ), + isBadRequest() ); - badRequest(() -> client.patch(apiPath(predefinedRole), patch(replaceOp("users", unparseableJsonRequest)))); + response = client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), unparseableJsonRequest))); + assertThat(response, isBadRequest()); + assertInvalidKeys(response, "unknown_json_property"); + assertThat(client.patch(apiPath(predefinedRole), patch(replaceOp("users", unparseableJsonRequest))), isBadRequest()); final var randomPropertyForPatch = randomJsonProperty(); - assertWrongDataType( - client.patch( - apiPath(), - patch( - addOp( - randomAsciiAlphanumOfLength(5), - (ToXContentObject) (builder, params) -> builder.startObject() - .field(randomPropertyForPatch) - .value("something") - .endObject() - ) + var resp2 = client.patch( + apiPath(), + patch( + addOp( + randomAsciiAlphanumOfLength(5), + (ToXContentObject) (builder, params) -> builder.startObject() + .field(randomPropertyForPatch) + .value("something") + .endObject() ) - ), - Map.of(randomPropertyForPatch, "Array expected") + ) ); + assertThat(resp2, isBadRequest().withAttribute("/status", "error").withAttribute("/" + randomPropertyForPatch, "Array expected")); // TODO related to issue #4426 - assertWrongDataType( - client.patch(apiPath(predefinedRole), patch(replaceOp("backend_roles", "something"))), - Map.of("backend_roles", "Array expected") - ); - assertWrongDataType(client.patch(apiPath(predefinedRole), patch(addOp("hosts", "something"))), Map.of("hosts", "Array expected")); - assertWrongDataType(client.patch(apiPath(predefinedRole), patch(addOp("users", "something"))), Map.of("users", "Array expected")); - assertWrongDataType( - client.patch(apiPath(predefinedRole), patch(addOp("and_backend_roles", "something"))), - Map.of("and_backend_roles", "Array expected") - ); + var resp3 = client.patch(apiPath(predefinedRole), patch(replaceOp("backend_roles", "something"))); + assertThat(resp3, isBadRequest().withAttribute("/status", "error").withAttribute("/backend_roles", "Array expected")); + var resp4 = client.patch(apiPath(predefinedRole), patch(addOp("hosts", "something"))); + assertThat(resp4, isBadRequest().withAttribute("/status", "error").withAttribute("/hosts", "Array expected")); + var resp5 = client.patch(apiPath(predefinedRole), patch(addOp("users", "something"))); + assertThat(resp5, isBadRequest().withAttribute("/status", "error").withAttribute("/users", "Array expected")); + var resp6 = client.patch(apiPath(predefinedRole), patch(addOp("and_backend_roles", "something"))); + assertThat(resp6, isBadRequest().withAttribute("/status", "error").withAttribute("/and_backend_roles", "Array expected")); assertNullValuesInArray( client.patch( apiPath(), @@ -420,22 +442,23 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { @Override void forbiddenToCreateEntityWithRestAdminPermissions(TestRestClient client) throws Exception { - forbidden(() -> client.putJson(apiPath(REST_ADMIN_ROLE), roleMappingWithUsers(randomArray(false)))); - forbidden(() -> client.patch(apiPath(), patch(addOp(REST_ADMIN_ROLE, roleMappingWithUsers(randomArray(false)))))); + assertThat(client.putJson(apiPath(REST_ADMIN_ROLE), roleMappingWithUsers(randomArray(false))), isForbidden()); + assertThat(client.patch(apiPath(), patch(addOp(REST_ADMIN_ROLE, roleMappingWithUsers(randomArray(false))))), isForbidden()); } @Override void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(TestRestClient client) throws Exception { // update - forbidden( - () -> client.putJson( + assertThat( + client.putJson( apiPath(REST_ADMIN_ROLE_WITH_MAPPING), roleMapping(randomArray(false), randomArray(false), randomArray(false), randomArray(false)) - ) + ), + isForbidden() ); - forbidden( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( replaceOp( @@ -443,13 +466,14 @@ void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(TestRestCl roleMapping(randomArray(false), randomArray(false), randomArray(false), randomArray(false)) ) ) - ) + ), + isForbidden() ); - forbidden(() -> client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(replaceOp("users", randomArray(false))))); + assertThat(client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(replaceOp("users", randomArray(false)))), isForbidden()); // remove - forbidden(() -> client.patch(apiPath(), patch(removeOp(REST_ADMIN_ROLE_WITH_MAPPING)))); - forbidden(() -> client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(removeOp("users")))); - forbidden(() -> client.delete(apiPath(REST_ADMIN_ROLE_WITH_MAPPING))); + assertThat(client.patch(apiPath(), patch(removeOp(REST_ADMIN_ROLE_WITH_MAPPING))), isForbidden()); + assertThat(client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(removeOp("users"))), isForbidden()); + assertThat(client.delete(apiPath(REST_ADMIN_ROLE_WITH_MAPPING)), isForbidden()); } String randomJsonProperty() { diff --git a/src/integrationTest/java/org/opensearch/security/api/RolesRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/RolesRestApiIntegrationTest.java index f52fe5fbfd..a0b8234fc4 100644 --- a/src/integrationTest/java/org/opensearch/security/api/RolesRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/RolesRestApiIntegrationTest.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.ClassRule; +import org.junit.Test; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.common.Strings; @@ -26,6 +28,7 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.dlic.rest.api.Endpoint; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.CoreMatchers.is; @@ -35,6 +38,11 @@ import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.removeOp; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class RolesRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { @@ -42,10 +50,12 @@ public class RolesRestApiIntegrationTest extends AbstractConfigEntityApiIntegrat private final static String REST_ADMIN_PERMISSION_ROLE = "rest-admin-permission-role"; - static { - testSecurityConfig.withRestAdminUser(REST_API_ADMIN_ACTION_ROLES_ONLY, restAdminPermission(Endpoint.ROLES)) - .roles(new TestSecurityConfig.Role(REST_ADMIN_PERMISSION_ROLE).clusterPermissions(allRestAdminPermissions())); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users( + new TestSecurityConfig.User(REST_API_ADMIN_ACTION_ROLES_ONLY).roles( + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.ROLES)) + ) + ).roles(new TestSecurityConfig.Role(REST_ADMIN_PERMISSION_ROLE).clusterPermissions(allRestAdminPermissions())).build(); public RolesRestApiIntegrationTest() { super("roles", new TestDescriptor() { @@ -71,27 +81,47 @@ public Optional restAdminLimitedUser() { }); } + @Test + public void forbiddenForRegularUsers() throws Exception { + super.forbiddenForRegularUsers(localCluster); + } + + @Test + public void availableForAdminUser() throws Exception { + super.availableForAdminUser(localCluster); + } + + @Test + public void availableForTLSAdminUser() throws Exception { + super.availableForTLSAdminUser(localCluster); + } + + @Test + public void availableForRESTAdminUser() throws Exception { + super.availableForRESTAdminUser(localCluster); + } + @Override void verifyCrudOperations(final Boolean hidden, final Boolean reserved, final TestRestClient client) throws Exception { final var newRoleJson = Strings.toString( XContentType.JSON, role(hidden, reserved, randomClusterPermissions(false), randomIndexPermissions(false), randomTenantPermissions(false)) ); - created(() -> client.putJson(apiPath("new_role"), newRoleJson)); + assertThat(client.putJson(apiPath("new_role"), newRoleJson), isCreated()); assertRole(ok(() -> client.get(apiPath("new_role"))), "new_role", hidden, reserved, newRoleJson); final var updatedRoleJson = Strings.toString( XContentType.JSON, role(hidden, reserved, randomClusterPermissions(false), randomIndexPermissions(false), randomTenantPermissions(false)) ); - ok(() -> client.putJson(apiPath("new_role"), updatedRoleJson)); + assertThat(client.putJson(apiPath("new_role"), updatedRoleJson), isOk()); assertRole(ok(() -> client.get(apiPath("new_role"))), "new_role", hidden, reserved, updatedRoleJson); - ok(() -> client.delete(apiPath("new_role"))); - notFound(() -> client.get(apiPath("new_role"))); + assertThat(client.delete(apiPath("new_role")), isOk()); + assertThat(client.get(apiPath("new_role")), isNotFound()); final var roleForPatch = role(hidden, reserved, configJsonArray("a", "b"), configJsonArray(), configJsonArray()); - ok(() -> client.patch(apiPath(), patch(addOp("new_role_for_patch", roleForPatch)))); + assertThat(client.patch(apiPath(), patch(addOp("new_role_for_patch", roleForPatch))), isOk()); assertRole( ok(() -> client.get(apiPath("new_role_for_patch"))), "new_role_for_patch", @@ -101,47 +131,43 @@ void verifyCrudOperations(final Boolean hidden, final Boolean reserved, final Te ); // TODO related to issue #4426 - ok( - () -> client.patch(apiPath("new_role_for_patch"), patch(replaceOp("cluster_permissions", configJsonArray("a", "b")))), - "No updates required" - ); - ok( - () -> client.patch(apiPath("new_role_for_patch"), patch(replaceOp("cluster_permissions", configJsonArray("a", "b", "c")))), - "'new_role_for_patch' updated." + assertThat(client.patch(apiPath("new_role_for_patch"), patch(replaceOp("cluster_permissions", configJsonArray("a", "b")))), isOk()); + assertThat( + client.patch(apiPath("new_role_for_patch"), patch(replaceOp("cluster_permissions", configJsonArray("a", "b", "c")))), + isOk() ); - ok(() -> client.patch(apiPath("new_role_for_patch"), patch(addOp("index_permissions", randomIndexPermissions(false))))); - ok(() -> client.patch(apiPath("new_role_for_patch"), patch(addOp("tenant_permissions", randomTenantPermissions(false))))); - - ok(() -> client.patch(apiPath(), patch(removeOp("new_role_for_patch")))); - notFound(() -> client.get(apiPath("new_role_for_patch"))); + assertThat(client.patch(apiPath("new_role_for_patch"), patch(addOp("index_permissions", randomIndexPermissions(false)))), isOk()); + assertThat(client.patch(apiPath("new_role_for_patch"), patch(addOp("tenant_permissions", randomTenantPermissions(false)))), isOk()); + assertThat(client.patch(apiPath(), patch(removeOp("new_role_for_patch"))), isOk()); + assertThat(client.get(apiPath("new_role_for_patch")), isNotFound()); } @Override void verifyBadRequestOperations(TestRestClient client) throws Exception { // put - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY)); - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + assertThat(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), isBadRequest()); + assertThat(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("cluster_permissions"); randomClusterPermissions(false).toXContent(builder, params); builder.field("cluster_permissions"); randomClusterPermissions(false).toXContent(builder, params); return builder.endObject(); - })); - assertInvalidKeys(badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + }), isBadRequest()); + assertInvalidKeys(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("unknown_json_property"); configJsonArray("a", "b").toXContent(builder, params); builder.field("cluster_permissions"); randomClusterPermissions(false).toXContent(builder, params); return builder.endObject(); - })), "unknown_json_property"); - assertWrongDataType(badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + }), "unknown_json_property"); + assertWrongDataType(client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("cluster_permissions").value("a"); builder.field("index_permissions").value("b"); return builder.endObject(); - })), Map.of("cluster_permissions", "Array expected", "index_permissions", "Array expected")); + }), Map.of("cluster_permissions", "Array expected", "index_permissions", "Array expected")); assertNullValuesInArray( client.putJson( apiPath(randomAsciiAlphanumOfLength(5)), @@ -150,18 +176,23 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { ); // patch final var predefinedRoleName = randomAsciiAlphanumOfLength(4); - created(() -> client.putJson(apiPath(predefinedRoleName), role(configJsonArray("a", "b"), configJsonArray(), configJsonArray()))); + assertThat( + client.putJson(apiPath(predefinedRoleName), role(configJsonArray("a", "b"), configJsonArray(), configJsonArray())), + isCreated() + ); - badRequest(() -> client.patch(apiPath(), patch(addOp("some_new_role", EMPTY_BODY)))); - badRequest( - () -> client.patch( + assertThat(client.patch(apiPath(), patch(addOp("some_new_role", EMPTY_BODY))), isBadRequest()); + + assertThat( + client.patch( apiPath(predefinedRoleName), patch(replaceOp(randomFrom(List.of("cluster_permissions", "index_permissions", "tenant_permissions")), EMPTY_BODY)) - ) + ), + isBadRequest() ); - badRequest( - () -> client.patch( + assertThat( + client.patch( apiPath(randomAsciiAlphanumOfLength(5)), patch(addOp(randomAsciiAlphanumOfLength(5), (ToXContentObject) (builder, params) -> { builder.startObject(); @@ -171,29 +202,34 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { randomClusterPermissions(false).toXContent(builder, params); return builder.endObject(); })) - ) + ), + isBadRequest() ); - badRequest(() -> client.patch(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { + + assertThat(client.patch(apiPath(randomAsciiAlphanumOfLength(5)), (builder, params) -> { builder.startObject(); builder.field("unknown_json_property"); configJsonArray("a", "b").toXContent(builder, params); builder.field("cluster_permissions"); randomClusterPermissions(false).toXContent(builder, params); return builder.endObject(); - })); - assertWrongDataType( - badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), (ToXContentObject) (builder, params) -> { - builder.startObject(); - builder.field("cluster_permissions").value("a"); - builder.field("index_permissions").value("b"); - return builder.endObject(); - })))), - Map.of("cluster_permissions", "Array expected", "index_permissions", "Array expected") - ); - assertWrongDataType( - badRequest(() -> client.patch(apiPath(predefinedRoleName), patch(replaceOp("cluster_permissions", "true")))), - Map.of("cluster_permissions", "Array expected") + }), isBadRequest()); + + var response = client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(5), (ToXContentObject) (builder, params) -> { + builder.startObject(); + builder.field("cluster_permissions").value("a"); + builder.field("index_permissions").value("b"); + return builder.endObject(); + }))); + assertThat( + response, + isBadRequest().withAttribute("/status", "error") + .withAttribute("/cluster_permissions", "Array expected") + .withAttribute("/index_permissions", "Array expected") ); + + response = badRequest(() -> client.patch(apiPath(predefinedRoleName), patch(replaceOp("cluster_permissions", "true")))); + assertThat(response, isBadRequest().withAttribute("/status", "error").withAttribute("/cluster_permissions", "Array expected")); assertNullValuesInArray( client.patch( apiPath(), @@ -213,26 +249,25 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { @Override void forbiddenToCreateEntityWithRestAdminPermissions(final TestRestClient client) throws Exception { - forbidden(() -> client.putJson(apiPath("new_rest_admin_role"), roleWithClusterPermissions(randomRestAdminPermission()))); - forbidden( - () -> client.patch( - apiPath(), - patch(addOp("new_rest_admin_action_group", roleWithClusterPermissions(randomRestAdminPermission()))) - ) + assertThat(client.putJson(apiPath("new_rest_admin_role"), roleWithClusterPermissions(randomRestAdminPermission())), isForbidden()); + assertThat( + client.patch(apiPath(), patch(addOp("new_rest_admin_action_group", roleWithClusterPermissions(randomRestAdminPermission())))), + isForbidden() ); } @Override void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final TestRestClient client) throws Exception { // update - forbidden( - () -> client.putJson( + assertThat( + client.putJson( apiPath(REST_ADMIN_PERMISSION_ROLE), role(randomClusterPermissions(false), randomIndexPermissions(false), randomTenantPermissions(false)) - ) + ), + isForbidden() ); - forbidden( - () -> client.patch( + assertThat( + client.patch( apiPath(), patch( replaceOp( @@ -240,18 +275,17 @@ void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(final Test role(randomClusterPermissions(false), randomIndexPermissions(false), randomTenantPermissions(false)) ) ) - ) + ), + isForbidden() ); - forbidden( - () -> client.patch( - apiPath(REST_ADMIN_PERMISSION_ROLE), - patch(replaceOp("cluster_permissions", randomClusterPermissions(false))) - ) + assertThat( + client.patch(apiPath(REST_ADMIN_PERMISSION_ROLE), patch(replaceOp("cluster_permissions", randomClusterPermissions(false)))), + isForbidden() ); // remove - forbidden(() -> client.patch(apiPath(), patch(removeOp(REST_ADMIN_PERMISSION_ROLE)))); - forbidden(() -> client.patch(apiPath(REST_ADMIN_PERMISSION_ROLE), patch(removeOp("cluster_permissions")))); - forbidden(() -> client.delete(apiPath(REST_ADMIN_PERMISSION_ROLE))); + assertThat(client.patch(apiPath(), patch(removeOp(REST_ADMIN_PERMISSION_ROLE))), isForbidden()); + assertThat(client.patch(apiPath(REST_ADMIN_PERMISSION_ROLE), patch(removeOp("cluster_permissions"))), isForbidden()); + assertThat(client.delete(apiPath(REST_ADMIN_PERMISSION_ROLE)), isForbidden()); } void assertRole( diff --git a/src/integrationTest/java/org/opensearch/security/api/RollbackVersionApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/RollbackVersionApiIntegrationTest.java index 330d6baf72..82fda7c18f 100644 --- a/src/integrationTest/java/org/opensearch/security/api/RollbackVersionApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/RollbackVersionApiIntegrationTest.java @@ -11,22 +11,26 @@ package org.opensearch.security.api; -import java.util.Map; - -import org.apache.http.HttpStatus; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isOneOf; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.support.ConfigConstants.EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; +import static org.opensearch.test.framework.matcher.RestMatchers.isUnauthorized; public class RollbackVersionApiIntegrationTest extends AbstractApiIntegrationTest { @@ -38,61 +42,57 @@ private String RollbackVersion(String versionId) { return ROLLBACK_BASE + "/" + versionId; } - @Override - protected Map getClusterSettings() { - Map settings = super.getClusterSettings(); - settings.put(EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED, true); - return settings; - } + @Rule + public LocalCluster localCluster = clusterBuilder().nodeSetting(EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED, true).build(); @Before public void setupConfigVersionsIndex() throws Exception { - try (TestRestClient client = localCluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) { - client.createUser(USER.getName(), USER).assertStatusCode(201); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + assertThat(client.createUser(USER.getName(), USER), anyOf(isOk(), isCreated())); } } @Test public void testRollbackToPreviousVersion_success() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { var response = client.post(ROLLBACK_BASE); - assertThat(response.getStatusCode(), is(HttpStatus.SC_OK)); + assertThat(response, isOk()); assertThat(response.getTextFromJsonBody("/status"), equalTo("OK")); assertThat(response.getTextFromJsonBody("/message"), containsString("config rolled back to version")); - }); + } } @Test public void testRollbackToSpecificVersion_success() throws Exception { String versionId = "v1"; - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { var response = client.post(RollbackVersion(versionId)); - assertThat(response.getStatusCode(), is(HttpStatus.SC_OK)); + assertThat(response, isOk()); assertThat(response.getTextFromJsonBody("/status"), equalTo("OK")); assertThat(response.getTextFromJsonBody("/message"), containsString("config rolled back to version " + versionId)); - }); + } } @Test public void testRollbackWithNonAdmin_shouldBeUnauthorized() throws Exception { - withUser(NEW_USER, DEFAULT_PASSWORD, client -> { + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { var response = client.post(ROLLBACK_BASE); - assertThat(response.getStatusCode(), isOneOf(HttpStatus.SC_FORBIDDEN, HttpStatus.SC_UNAUTHORIZED)); - }); + assertThat(response, anyOf(isForbidden(), isUnauthorized())); + } } @Test public void testRollbackToInvalidVersion_shouldReturnNotFound() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { var response = client.post(RollbackVersion("does-not-exist")); - assertThat(response.getStatusCode(), is(HttpStatus.SC_NOT_FOUND)); + assertThat(response, isNotFound()); assertThat(response.getTextFromJsonBody("/message"), containsString("not found")); - }); + } } @Test public void testRollbackWhenOnlyOneVersion_shouldFail() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { // To perform below test, delete all entries in .opensearch_security_config_versions index String deleteQuery = """ { @@ -116,7 +116,7 @@ public void testRollbackWhenOnlyOneVersion_shouldFail() throws Exception { var response = client.post(ROLLBACK_BASE); assertThat(response.getStatusCode(), is(404)); assertThat(response.getBody(), containsString("No previous version available to rollback")); - }); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/api/SslCertsRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/SslCertsRestApiIntegrationTest.java index bbdd9ff793..8c911d3161 100644 --- a/src/integrationTest/java/org/opensearch/security/api/SslCertsRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/SslCertsRestApiIntegrationTest.java @@ -10,34 +10,35 @@ */ package org.opensearch.security.api; -import java.util.Map; - import com.fasterxml.jackson.databind.JsonNode; +import org.junit.ClassRule; import org.junit.Test; import org.opensearch.security.dlic.rest.api.Endpoint; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.CERTS_INFO_ACTION; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; @Deprecated public class SslCertsRestApiIntegrationTest extends AbstractApiIntegrationTest { final static String REST_API_ADMIN_SSL_INFO = "rest-api-admin-ssl-info"; - static { - testSecurityConfig.withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions()) - .withRestAdminUser(REST_API_ADMIN_SSL_INFO, restAdminPermission(Endpoint.SSL, CERTS_INFO_ACTION)); - } - - @Override - protected Map getClusterSettings() { - Map clusterSettings = super.getClusterSettings(); - clusterSettings.put(SECURITY_RESTAPI_ADMIN_ENABLED, true); - return clusterSettings; - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().nodeSetting(SECURITY_RESTAPI_ADMIN_ENABLED, true) + .users( + new TestSecurityConfig.User(REST_API_ADMIN_SSL_INFO).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.SSL, CERTS_INFO_ACTION)) + ) + ) + .build(); protected String sslCertsPath() { return super.apiPath("ssl", "certs"); @@ -45,27 +46,38 @@ protected String sslCertsPath() { @Test public void certsInfoForbiddenForRegularUser() throws Exception { - withUser(NEW_USER, client -> forbidden(() -> client.get(sslCertsPath()))); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(sslCertsPath()), isForbidden()); + } } @Test public void certsInfoForbiddenForAdminUser() throws Exception { - withUser(NEW_USER, client -> forbidden(() -> client.get(sslCertsPath()))); + try (TestRestClient client = localCluster.getRestClient(NEW_USER)) { + assertThat(client.get(sslCertsPath()), isForbidden()); + } } @Test public void certsInfoAvailableForTlsAdmin() throws Exception { - withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::verifySSLCertsInfo); + try (TestRestClient client = localCluster.getAdminCertRestClient()) { + verifySSLCertsInfo(client); + } } @Test public void certsInfoAvailableForRestAdmin() throws Exception { - withUser(REST_ADMIN_USER, this::verifySSLCertsInfo); - withUser(REST_API_ADMIN_SSL_INFO, this::verifySSLCertsInfo); + try (TestRestClient client = localCluster.getRestClient(REST_ADMIN_USER)) { + verifySSLCertsInfo(client); + } + try (TestRestClient client = localCluster.getRestClient(REST_API_ADMIN_SSL_INFO, DEFAULT_PASSWORD)) { + verifySSLCertsInfo(client); + } } private void verifySSLCertsInfo(final TestRestClient client) throws Exception { - final var response = ok(() -> client.get(sslCertsPath())); + final var response = client.get(sslCertsPath()); + assertThat(response, isOk()); final var body = response.bodyAsJsonNode(); assertThat(response.getBody(), body.has("http_certificates_list")); diff --git a/src/integrationTest/java/org/opensearch/security/api/TenantsRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/TenantsRestApiIntegrationTest.java index cb3431be79..7dbf74781d 100644 --- a/src/integrationTest/java/org/opensearch/security/api/TenantsRestApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/TenantsRestApiIntegrationTest.java @@ -14,9 +14,13 @@ import java.util.Optional; import com.fasterxml.jackson.databind.JsonNode; +import org.junit.ClassRule; +import org.junit.Test; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.security.dlic.rest.api.Endpoint; +import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.CoreMatchers.is; @@ -25,14 +29,22 @@ import static org.opensearch.security.api.PatchPayloadHelper.patch; import static org.opensearch.security.api.PatchPayloadHelper.removeOp; import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; +import static org.opensearch.test.framework.matcher.RestMatchers.isBadRequest; +import static org.opensearch.test.framework.matcher.RestMatchers.isCreated; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; public class TenantsRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { private final static String REST_API_ADMIN_TENANTS_ONLY = "rest_api_admin_tenants_only"; - static { - testSecurityConfig.withRestAdminUser(REST_API_ADMIN_TENANTS_ONLY, restAdminPermission(Endpoint.TENANTS)); - } + @ClassRule + public static LocalCluster localCluster = clusterBuilder().users( + new TestSecurityConfig.User(REST_API_ADMIN_TENANTS_ONLY).roles( + REST_ADMIN_REST_API_ACCESS_ROLE, + new TestSecurityConfig.Role("rest_admin_role").clusterPermissions(restAdminPermission(Endpoint.TENANTS)) + ) + ).build(); public TenantsRestApiIntegrationTest() { super("tenants", new TestDescriptor() { @@ -83,15 +95,36 @@ static ToXContentObject tenant(final Boolean hidden, final Boolean reserved, fin }; } + @Test + public void forbiddenForRegularUsers() throws Exception { + super.forbiddenForRegularUsers(localCluster); + } + + @Test + public void availableForAdminUser() throws Exception { + super.availableForAdminUser(localCluster); + } + + @Test + public void availableForTLSAdminUser() throws Exception { + super.availableForTLSAdminUser(localCluster); + } + + @Test + public void availableForRESTAdminUser() throws Exception { + super.availableForRESTAdminUser(localCluster); + } + @Override void verifyBadRequestOperations(TestRestClient client) throws Exception { // put - badRequest(() -> client.putJson(apiPath(randomAsciiAlphanumOfLength(4)), EMPTY_BODY)); - badRequest( - () -> client.putJson( + assertThat(client.putJson(apiPath(randomAsciiAlphanumOfLength(4)), EMPTY_BODY), isBadRequest()); + assertThat( + client.putJson( apiPath(randomAsciiAlphanumOfLength(4)), (builder, params) -> builder.startObject().field("description", "a").field("description", "b").endObject() - ) + ), + isBadRequest() ); assertInvalidKeys( client.putJson( @@ -101,9 +134,9 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { "a,c" ); // patch - badRequest(() -> client.patch(apiPath(), EMPTY_BODY)); - badRequest( - () -> client.patch( + assertThat(client.patch(apiPath(), EMPTY_BODY), isBadRequest()); + assertThat( + client.patch( apiPath(), patch( addOp( @@ -114,7 +147,8 @@ void verifyBadRequestOperations(TestRestClient client) throws Exception { .endObject() ) ) - ) + ), + isBadRequest() ); assertInvalidKeys( client.patch( @@ -140,23 +174,23 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien // put final var putDescription = randomAsciiAlphanumOfLength(10); final var putTenantName = randomAsciiAlphanumOfLength(4); - created(() -> client.putJson(apiPath(putTenantName), tenant(hidden, reserved, putDescription))); - assertTenant(ok(() -> client.get(apiPath(putTenantName))).bodyAsJsonNode().get(putTenantName), hidden, reserved, putDescription); + assertThat(client.putJson(apiPath(putTenantName), tenant(hidden, reserved, putDescription)), isCreated()); + assertTenant(client.get(apiPath(putTenantName)).bodyAsJsonNode().get(putTenantName), hidden, reserved, putDescription); final var putUpdatedDescription = randomAsciiAlphanumOfLength(10); - ok(() -> client.putJson(apiPath(putTenantName), tenant(hidden, reserved, putUpdatedDescription))); + assertThat(client.putJson(apiPath(putTenantName), tenant(hidden, reserved, putUpdatedDescription)), isOk()); assertTenant( ok(() -> client.get(apiPath(putTenantName))).bodyAsJsonNode().get(putTenantName), hidden, reserved, putUpdatedDescription ); - ok(() -> client.delete(apiPath(putTenantName))); - notFound(() -> client.get(apiPath(putTenantName))); + assertThat(client.delete(apiPath(putTenantName)), isOk()); + assertThat(client.get(apiPath(putTenantName)), isNotFound()); // patch final var patchTenantName = randomAsciiAlphanumOfLength(4); final var patchDescription = randomAsciiAlphanumOfLength(10); - ok(() -> client.patch(apiPath(), patch(addOp(patchTenantName, tenant(hidden, reserved, patchDescription))))); + assertThat(client.patch(apiPath(), patch(addOp(patchTenantName, tenant(hidden, reserved, patchDescription)))), isOk()); assertTenant( ok(() -> client.get(apiPath(patchTenantName))).bodyAsJsonNode().get(patchTenantName), hidden, @@ -165,7 +199,7 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien ); final var patchUpdatedDescription = randomAsciiAlphanumOfLength(10); - ok(() -> client.patch(apiPath(patchTenantName), patch(replaceOp("description", patchUpdatedDescription)))); + assertThat(client.patch(apiPath(patchTenantName), patch(replaceOp("description", patchUpdatedDescription))), isOk()); assertTenant( ok(() -> client.get(apiPath(patchTenantName))).bodyAsJsonNode().get(patchTenantName), hidden, @@ -173,8 +207,8 @@ void verifyCrudOperations(Boolean hidden, Boolean reserved, TestRestClient clien patchUpdatedDescription ); - ok(() -> client.patch(apiPath(), patch(removeOp(patchTenantName)))); - notFound(() -> client.get(apiPath(patchTenantName))); + assertThat(client.patch(apiPath(), patch(removeOp(patchTenantName))), isOk()); + assertThat(client.get(apiPath(patchTenantName)), isNotFound()); } void assertTenant(final JsonNode actualJson, final Boolean hidden, final Boolean reserved, final String expectedDescription) { diff --git a/src/integrationTest/java/org/opensearch/security/api/ViewVersionApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/ViewVersionApiIntegrationTest.java index 67a7627549..13274cb20c 100644 --- a/src/integrationTest/java/org/opensearch/security/api/ViewVersionApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/ViewVersionApiIntegrationTest.java @@ -11,28 +11,33 @@ package org.opensearch.security.api; -import java.util.Map; - import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.opensearch.test.framework.TestSecurityConfig; +import org.opensearch.test.framework.cluster.LocalCluster; import org.opensearch.test.framework.cluster.TestRestClient; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isOneOf; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.support.ConfigConstants.EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED; +import static org.opensearch.test.framework.matcher.RestMatchers.isForbidden; +import static org.opensearch.test.framework.matcher.RestMatchers.isNotFound; +import static org.opensearch.test.framework.matcher.RestMatchers.isOk; +import static org.opensearch.test.framework.matcher.RestMatchers.isUnauthorized; public class ViewVersionApiIntegrationTest extends AbstractApiIntegrationTest { - static { - testSecurityConfig.user(new TestSecurityConfig.User("limitedUser").password("limitedPass")); - } + @Rule + public LocalCluster localCluster = clusterBuilder().users(new TestSecurityConfig.User("limitedUser").password("limitedPass")) + .nodeSetting(EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED, true) + .build(); private static final TestSecurityConfig.User USER = new TestSecurityConfig.User("user"); @@ -48,37 +53,32 @@ private String viewVersion(String versionId) { return endpointPrefix() + "/version/" + versionId; } - @Override - protected Map getClusterSettings() { - Map settings = super.getClusterSettings(); - settings.put(EXPERIMENTAL_SECURITY_CONFIGURATIONS_VERSIONS_ENABLED, true); - return settings; - } - @Before public void setupIndexAndCerts() throws Exception { - try (TestRestClient client = localCluster.getRestClient(ADMIN_USER_NAME, DEFAULT_PASSWORD)) { + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { client.createUser(USER.getName(), USER).assertStatusCode(201); } } @Test public void testViewAllVersions() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { - var response = ok(() -> client.get(viewVersionBase())); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + var response = client.get(viewVersionBase()); + assertThat(response, isOk()); var json = response.bodyAsJsonNode(); assertThat(json.has("versions"), is(true)); var versions = json.get("versions"); assertThat(versions.isArray(), is(true)); assertThat(versions.size(), greaterThan(0)); - }); + } } @Test public void testViewSpecificVersionFound() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { - var response = ok(() -> client.get(viewVersion("v1"))); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + var response = client.get(viewVersion("v1")); + assertThat(response, isOk()); var json = response.bodyAsJsonNode(); assertThat(json.has("versions"), is(true)); @@ -88,13 +88,14 @@ public void testViewSpecificVersionFound() throws Exception { var ver = versions.get(0); assertThat(ver.get("version_id").asText(), equalTo("v1")); - }); + } } @Test public void testViewSpecificVersionNotFound() throws Exception { - withUser(ADMIN_USER_NAME, DEFAULT_PASSWORD, client -> { - var response = notFound(() -> client.get(viewVersion("does-not-exist"))); + try (TestRestClient client = localCluster.getRestClient(ADMIN_USER)) { + var response = client.get(viewVersion("does-not-exist")); + assertThat(response, isNotFound()); var json = response.bodyAsJsonNode(); assertThat(json.has("status"), is(true)); @@ -102,14 +103,14 @@ public void testViewSpecificVersionNotFound() throws Exception { assertThat(json.has("message"), is(true)); assertThat(json.get("message").asText(), containsString("not found")); - }); + } } @Test public void testViewAllVersions_forbiddenWithoutAdminCert() throws Exception { - withUser("limitedUser", "limitedPass", client -> { + try (TestRestClient client = localCluster.getRestClient("limitedUser", "limitedPass")) { var response = client.get(viewVersionBase()); - assertThat(response.getStatusCode(), isOneOf(401, 403)); - }); + assertThat(response, anyOf(isUnauthorized(), isForbidden())); + } } } diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 4f94af9796..76b9332835 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -35,6 +35,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -73,7 +74,6 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.test.framework.cluster.OpenSearchClientProvider.UserCredentialsHolder; import org.opensearch.test.framework.data.TestIndex; -import org.opensearch.test.framework.matcher.RestIndexMatchers; import org.opensearch.transport.client.Client; import static org.apache.http.HttpHeaders.AUTHORIZATION; @@ -92,8 +92,6 @@ */ public class TestSecurityConfig { - public static final String REST_ADMIN_REST_API_ACCESS = "rest_admin__rest_api_access"; - private static final Logger log = LogManager.getLogger(TestSecurityConfig.class); private static final PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher( @@ -163,11 +161,7 @@ public TestSecurityConfig authz(AuthzDomain authzDomain) { public TestSecurityConfig user(User user) { this.internalUsers.put(user.name, user); - - for (Role role : user.roles) { - this.roles.put(role.name, role); - } - + // The user's roles will be collected by aggregateRoles() when the configuration is written return this; } @@ -178,18 +172,6 @@ public TestSecurityConfig users(User... users) { return this; } - public TestSecurityConfig withRestAdminUser(final String name, final String... permissions) { - if (!internalUsers.containsKey(name)) { - user(new User(name).description("REST Admin with permissions: " + Arrays.toString(permissions)).reserved(true)); - final var roleName = name + "__rest_admin_role"; - roles(new Role(roleName).clusterPermissions(permissions)); - - rolesMapping.computeIfAbsent(roleName, RoleMapping::new).users(name); - rolesMapping.computeIfAbsent(REST_ADMIN_REST_API_ACCESS, RoleMapping::new).users(name); - } - return this; - } - public List getUsers() { return new ArrayList<>(internalUsers.values()); } @@ -199,6 +181,7 @@ public TestSecurityConfig roles(Role... roles) { if (this.roles.containsKey(role.name)) { throw new IllegalStateException("Role with name " + role.name + " is already defined"); } + role.addedIndependentlyOfUser = true; this.roles.put(role.name, role); } @@ -457,10 +440,13 @@ public static final class User implements UserCredentialsHolder, ToXContentObjec private String password; List roles = new ArrayList<>(); List backendRoles = new ArrayList<>(); + /** + * This will be initialized by aggregateRoles() + */ + Set roleNames; String requestedTenant; private Map attributes = new HashMap<>(); private Map, Object> matchers = new HashMap<>(); - private Map indexMatchers = new HashMap<>(); private boolean adminCertUser = false; private Boolean hidden = null; @@ -487,11 +473,7 @@ public User password(String password) { } public User roles(Role... roles) { - // We scope the role names by user to keep tests free of potential side effects - String roleNamePrefix = "user_" + this.getName() + "__"; - this.roles.addAll( - Arrays.asList(roles).stream().map((r) -> r.clone().name(roleNamePrefix + r.getName())).collect(Collectors.toSet()) - ); + this.roles.addAll(Arrays.asList(roles)); return this; } @@ -538,7 +520,10 @@ public String getPassword() { } public Set getRoleNames() { - return roles.stream().map(Role::getName).collect(Collectors.toSet()); + if (roleNames == null) { + this.aggregateRoles(); + } + return roleNames; } public String getDescription() { @@ -641,6 +626,25 @@ public MetadataKey(String name, Class type) { this.type = type; } } + + void aggregateRoles() { + if (this.roleNames == null) { + this.roleNames = new HashSet<>(); + } + + for (Role role : this.roles) { + if (role.addedIndependentlyOfUser) { + // This is a globally defined role, we just use this + this.roleNames.add(role.name); + } else { + // This is role that is locally defined for the user; let's scope the name + if (!role.name.startsWith("user_" + this.name)) { + role.name = "user_" + this.name + "__" + role.name; + } + this.roleNames.add(role.name); + } + } + } } public static class Role implements ToXContentObject { @@ -657,6 +661,12 @@ public static class Role implements ToXContentObject { private String description; + /** + * This will be set to true, if this was added using the roles() method on TestSecurityConfig. + * Then, we will consider this a role which is shared between users and we won't scope its name. + */ + private boolean addedIndependentlyOfUser = false; + public Role(String name) { this(name, null); } @@ -1095,7 +1105,7 @@ public void initIndex(Client client) { if (auditConfiguration != null) { writeSingleEntryConfigToIndex(client, CType.AUDIT, "config", auditConfiguration); } - writeConfigToIndex(client, CType.ROLES, roles); + writeConfigToIndex(client, CType.ROLES, aggregateRoles()); writeConfigToIndex(client, CType.INTERNALUSERS, internalUsers); writeConfigToIndex(client, CType.ROLESMAPPING, rolesMapping); writeConfigToIndex(client, CType.ACTIONGROUPS, actionGroups); @@ -1107,7 +1117,25 @@ public void initIndex(Client client) { writeConfigToIndex(client, entry.getKey(), entry.getValue()); } } + } + + /** + * Merges the globally defined roles with the roles defined by user. Roles defined by user will be scoped + * so that user definitions cannot interfere with others. + */ + private Map aggregateRoles() { + Map result = new HashMap<>(this.roles); + for (User user : this.internalUsers.values()) { + user.aggregateRoles(); + + for (Role role : user.roles) { + if (!role.addedIndependentlyOfUser) { + result.put(role.name, role); + } + } + } + return result; } public void updateInternalUsersConfiguration(Client client, List users) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index 5aef9f29b4..37f7a0c054 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -450,6 +450,11 @@ public Builder nodeSettings(Map settings) { return this; } + public Builder nodeSetting(String key, Object value) { + nodeOverrideSettingsBuilder.put(key, String.valueOf(value)); + return this; + } + public Builder nodeSpecificSettings(int nodeNumber, Map settings) { if (!nodeSpecificOverrideSettingsBuilder.containsKey(nodeNumber)) { Settings.Builder builderCopy = Settings.builder(); diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java index 96faab57c1..20dc5b59d5 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/RestMatchers.java @@ -66,6 +66,14 @@ public static OpenSearchErrorHttpResponseMatcher isNotFound() { return new OpenSearchErrorHttpResponseMatcher(404, "Not Found"); } + public static OpenSearchErrorHttpResponseMatcher isNotAllowed() { + return new OpenSearchErrorHttpResponseMatcher(405, "Not Allowed"); + } + + public static OpenSearchErrorHttpResponseMatcher isUnauthorized() { + return new OpenSearchErrorHttpResponseMatcher(401, "Unauthorized"); + } + public static class HttpResponseMatcher extends DiagnosingMatcher { final int statusCode; final String statusName;