From 492011b1d241b29c671bc6ffa66cad14b38d7f85 Mon Sep 17 00:00:00 2001 From: Andrey Pleskach Date: Wed, 12 Jun 2024 21:37:04 +0200 Subject: [PATCH] Refactor Roles mapping REST API test and partial fix #4166 Signed-off-by: Andrey Pleskach --- .../api/AbstractApiIntegrationTest.java | 6 + ...bstractConfigEntityApiIntegrationTest.java | 46 +- .../RolesMappingRestApiIntegrationTest.java | 481 +++++++++++++ .../dlic/rest/api/RolesMappingApiAction.java | 7 +- .../dlic/rest/api/RolesMappingApiTest.java | 669 ------------------ .../legacy/LegacyRolesMappingApiTests.java | 23 - 6 files changed, 522 insertions(+), 710 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java delete mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java delete mode 100644 src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java diff --git a/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java index adf10cd098..24883780e4 100644 --- a/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/AbstractApiIntegrationTest.java @@ -283,6 +283,12 @@ 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); diff --git a/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java index c852ecb272..a8ca33d539 100644 --- a/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java +++ b/src/integrationTest/java/org/opensearch/security/api/AbstractConfigEntityApiIntegrationTest.java @@ -22,6 +22,8 @@ import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.test.framework.cluster.TestRestClient; +import com.nimbusds.jose.util.Pair; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -133,23 +135,14 @@ public void forbiddenForRegularUsers() throws Exception { @Test public void availableForAdminUser() 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())) - ); - + final var entitiesNames = predefinedHiddenAndReservedConfigEntities(); + final var hiddenEntityName = entitiesNames.getLeft(); + final var reservedEntityName = entitiesNames.getRight(); // can't see hidden resources withUser(ADMIN_USER_NAME, client -> { verifyNoHiddenEntities(() -> client.get(apiPath())); creationOfReadOnlyEntityForbidden( + randomAsciiAlphanumOfLength(10), client, (builder, params) -> testDescriptor.hiddenEntityPayload().toXContent(builder, params), (builder, params) -> testDescriptor.reservedEntityPayload().toXContent(builder, params), @@ -162,6 +155,22 @@ public void availableForAdminUser() throws Exception { }); } + Pair predefinedHiddenAndReservedConfigEntities() 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())) + ); + return Pair.of(hiddenEntityName, reservedEntityName); + } + @Test public void availableForTLSAdminUser() throws Exception { withUser(ADMIN_USER_NAME, localCluster.getAdminCertificate(), this::availableForSuperAdminUser); @@ -176,7 +185,11 @@ public void availableForRESTAdminUser() throws Exception { } void availableForSuperAdminUser(final TestRestClient client) throws Exception { - creationOfReadOnlyEntityForbidden(client, (builder, params) -> testDescriptor.staticEntityPayload().toXContent(builder, params)); + creationOfReadOnlyEntityForbidden( + randomAsciiAlphanumOfLength(10), + client, + (builder, params) -> testDescriptor.staticEntityPayload().toXContent(builder, params) + ); verifyCrudOperations(true, null, client); verifyCrudOperations(null, true, client); verifyCrudOperations(null, null, client); @@ -195,10 +208,11 @@ void verifyNoHiddenEntities(final CheckedSupplier client.putJson(apiPath(randomAsciiAlphanumOfLength(10)), configEntity)), + badRequest(() -> client.putJson(apiPath(entityName), configEntity)), is(oneOf("static", "hidden", "reserved")) ); badRequest(() -> client.patch(apiPath(), patch(addOp(randomAsciiAlphanumOfLength(10), configEntity)))); diff --git a/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java b/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java new file mode 100644 index 0000000000..c6b40bdf11 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/api/RolesMappingRestApiIntegrationTest.java @@ -0,0 +1,481 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.api; + +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.opensearch.common.xcontent.XContentType; +import org.opensearch.core.common.Strings; +import org.opensearch.core.xcontent.ToXContentObject; +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.TestRestClient; + +import com.nimbusds.jose.util.Pair; + +import static org.hamcrest.CoreMatchers.is; +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.security.api.PatchPayloadHelper.removeOp; +import static org.opensearch.security.api.PatchPayloadHelper.replaceOp; +import static org.opensearch.test.framework.TestSecurityConfig.Role; + +public class RolesMappingRestApiIntegrationTest extends AbstractConfigEntityApiIntegrationTest { + + final static String REST_API_ADMIN_ROLES_MAPPING_ONLY = "rest-api-admin-roles-mapping-only"; + + 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)); + } + + public RolesMappingRestApiIntegrationTest() { + super("rolesmapping", new TestDescriptor() { + @Override + public ToXContentObject entityPayload(Boolean hidden, Boolean reserved, Boolean _static) { + return roleMapping( + hidden, + reserved, + _static, + configJsonArray("a", "b"), + configJsonArray("c", "d"), + configJsonArray("e", "f"), + configJsonArray("g", "h") + ); + } + + @Override + public String entityJsonProperty() { + return "backend_roles"; + } + + @Override + public ToXContentObject jsonPropertyPayload() { + return configJsonArray("a", "b"); + } + + @Override + public Optional restAdminLimitedUser() { + return Optional.of(REST_API_ADMIN_ROLES_MAPPING_ONLY); + } + }); + } + + static ToXContentObject roleMappingWithUsers(ToXContentObject users) { + return roleMapping(null, null, null, null, null, users, null); + } + + static ToXContentObject roleMapping( + ToXContentObject backendRoles, + ToXContentObject hosts, + ToXContentObject users, + ToXContentObject andBackendRoles + ) { + return roleMapping(null, null, null, backendRoles, hosts, users, andBackendRoles); + } + + static ToXContentObject roleMapping( + final Boolean hidden, + final Boolean reserved, + ToXContentObject backendRoles, + ToXContentObject hosts, + ToXContentObject users, + ToXContentObject andBackendRoles + ) { + return roleMapping(hidden, reserved, null, backendRoles, hosts, users, andBackendRoles); + } + + static ToXContentObject roleMapping( + final Boolean hidden, + final Boolean reserved, + final Boolean _static, + ToXContentObject backendRoles, + ToXContentObject hosts, + ToXContentObject users, + ToXContentObject andBackendRoles + ) { + return (builder, params) -> { + builder.startObject(); + if (hidden != null) { + builder.field("hidden", hidden); + } + if (reserved != null) { + builder.field("reserved", reserved); + } + if (_static != null) { + builder.field("static", _static); + } + + builder.field("backend_roles"); + if (backendRoles != null) { + backendRoles.toXContent(builder, params); + } else { + builder.startArray().endArray(); + } + + builder.field("hosts"); + if (hosts != null) { + hosts.toXContent(builder, params); + } else { + builder.startArray().endArray(); + } + + builder.field("users"); + if (users != null) { + users.toXContent(builder, params); + } else { + builder.startArray().endArray(); + } + + builder.field("and_backend_roles"); + if (andBackendRoles != null) { + andBackendRoles.toXContent(builder, params); + } else { + builder.startArray().endArray(); + } + + return builder.endObject(); + }; + } + + String rolesApiPath(final String roleName) { + return new StringJoiner("/").add(api()).add("roles").add(roleName).toString(); + } + + @Override + Pair predefinedHiddenAndReservedConfigEntities() 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( + 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( + apiPath(reservedEntityName), + roleMapping(null, true, null, configJsonArray("a", "b"), configJsonArray(), configJsonArray(), configJsonArray()) + ) + ) + ); + 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()))); + 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())); + // put + final var newPutRoleMappingJson = roleMapping( + hidden, + reserved, + randomArray(false), + randomArray(false), + randomArray(false), + randomArray(false) + ); + created(() -> client.putJson(apiPath(roleName), newPutRoleMappingJson)); + assertRoleMapping( + ok(() -> client.get(apiPath(roleName))).bodyAsJsonNode().get(roleName), + hidden, + reserved, + Strings.toString(XContentType.JSON, newPutRoleMappingJson) + ); + final var updatePutRoleMappingJson = roleMapping( + hidden, + reserved, + randomArray(false), + randomArray(false), + randomArray(false), + randomArray(false) + ); + ok(() -> client.putJson(apiPath(roleName), updatePutRoleMappingJson)); + assertRoleMapping( + ok(() -> client.get(apiPath(roleName))).bodyAsJsonNode().get(roleName), + hidden, + reserved, + Strings.toString(XContentType.JSON, updatePutRoleMappingJson) + ); + + ok(() -> client.delete(apiPath(roleName))); + notFound(() -> client.get(apiPath(roleName))); + // patch + // TODO related to issue #4426 + final var newPatchRoleMappingJson = roleMapping( + hidden, + reserved, + configJsonArray("a", "b"), + configJsonArray(), + configJsonArray(), + configJsonArray() + ); + ok(() -> client.patch(apiPath(), patch(addOp(roleName, newPatchRoleMappingJson)))); + 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(), patch(removeOp(roleName)))); + notFound(() -> client.get(apiPath(roleName))); + } + + void assertRoleMapping(final JsonNode actualObjectNode, final Boolean hidden, final Boolean reserved, final String expectedRoleJson) + throws IOException { + final var expectedObjectNode = DefaultObjectMapper.readTree(expectedRoleJson); + final var expectedHidden = hidden != null && hidden; + final var expectedReserved = reserved != null && reserved; + assertThat(actualObjectNode.toPrettyString(), actualObjectNode.get("hidden").asBoolean(), is(expectedHidden)); + assertThat(actualObjectNode.toPrettyString(), actualObjectNode.get("reserved").asBoolean(), is(expectedReserved)); + assertThat(actualObjectNode.toPrettyString(), actualObjectNode.get("backend_roles"), is(expectedObjectNode.get("backend_roles"))); + assertThat(actualObjectNode.toPrettyString(), actualObjectNode.get("hosts"), is(expectedObjectNode.get("hosts"))); + assertThat(actualObjectNode.toPrettyString(), actualObjectNode.get("users"), is(expectedObjectNode.get("users"))); + assertThat( + actualObjectNode.toPrettyString(), + actualObjectNode.get("and_backend_roles"), + is(expectedObjectNode.get("and_backend_roles")) + ); + } + + @Override + void verifyBadRequestOperations(TestRestClient client) throws Exception { + + final ToXContentObject unparseableJsonRequest = (builder, params) -> { + builder.startObject(); + builder.field("unknown_json_property"); + configJsonArray("a", "b").toXContent(builder, params); + builder.field("users"); + configJsonArray("a", "b").toXContent(builder, params); + return builder.endObject(); + }; + + notFound( + () -> client.putJson( + apiPath("unknown_role"), + roleMapping(configJsonArray(), configJsonArray(), configJsonArray(), configJsonArray()) + ), + "role 'unknown_role' not found." + ); + + // put + badRequestWithReason( + () -> client.putJson(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY), + "Request body required for this action." + ); + badRequestWithReason( + () -> 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" + ); + final var randomPropertyForPut = randomJsonProperty(); + assertWrongDataType( + client.putJson( + apiPath(randomAsciiAlphanumOfLength(5)), + (builder, params) -> builder.startObject().field(randomPropertyForPut).value("something").endObject() + ), + Map.of(randomPropertyForPut, "Array expected") + ); + assertNullValuesInArray( + client.putJson( + apiPath(randomAsciiAlphanumOfLength(5)), + roleMapping( + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)) + ) + ) + ); + // patch + final var predefinedRole = randomAsciiAlphanumOfLength(5); + created(() -> client.putJson(rolesApiPath(predefinedRole), roleJson())); + created( + () -> client.putJson( + apiPath(predefinedRole), + roleMapping(configJsonArray("a", "b"), configJsonArray(), configJsonArray(), configJsonArray()) + ) + ); + badRequest(() -> client.patch(apiPath(randomAsciiAlphanumOfLength(5)), EMPTY_BODY)); + badRequest( + () -> 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" + ); + badRequest(() -> client.patch(apiPath(predefinedRole), patch(replaceOp("users", unparseableJsonRequest)))); + final var randomPropertyForPatch = randomJsonProperty(); + assertWrongDataType( + client.patch( + apiPath(), + patch( + addOp( + randomAsciiAlphanumOfLength(5), + (ToXContentObject) (builder, params) -> builder.startObject() + .field(randomPropertyForPatch) + .value("something") + .endObject() + ) + ) + ), + Map.of(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") + ); + assertNullValuesInArray( + client.patch( + apiPath(), + patch( + addOp( + randomAsciiAlphanumOfLength(5), + roleMapping( + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)), + configJsonArray(generateArrayValues(true)) + ) + ) + ) + ) + ); + // TODO related to issue #4426 + assertNullValuesInArray( + client.patch(apiPath(predefinedRole), patch(replaceOp("backend_roles", configJsonArray(generateArrayValues(true))))) + ); + } + + @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)))))); + + } + + @Override + void forbiddenToUpdateAndDeleteExistingEntityWithRestAdminPermissions(TestRestClient client) throws Exception { + // update + forbidden( + () -> client.putJson( + apiPath(REST_ADMIN_ROLE_WITH_MAPPING), + roleMapping(randomArray(false), randomArray(false), randomArray(false), randomArray(false)) + ) + ); + forbidden( + () -> client.patch( + apiPath(), + patch( + replaceOp( + REST_ADMIN_ROLE_WITH_MAPPING, + roleMapping(randomArray(false), randomArray(false), randomArray(false), randomArray(false)) + ) + ) + ) + ); + forbidden(() -> client.patch(apiPath(REST_ADMIN_ROLE_WITH_MAPPING), patch(replaceOp("users", randomArray(false))))); + // 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))); + } + + String randomJsonProperty() { + return randomFrom(List.of("backend_roles", "hosts", "users", "and_backend_roles")); + } + + ToXContentObject roleJson() { + return roleJson(null, null); + } + + ToXContentObject roleJson(final Boolean hidden, final Boolean reserved) { + return (builder, params) -> { + builder.startObject(); + if (hidden != null) { + builder.field("hidden", hidden); + } + if (reserved != null) { + builder.field("reserved", reserved); + } + builder.field("cluster_permissions", configJsonArray("a", "b")); + return builder.endObject(); + }; + } + + ToXContentObject randomArray(final boolean useNulls) { + return useNulls + ? configJsonArray(generateArrayValues(useNulls)) + : randomFrom(List.of(configJsonArray(generateArrayValues(false)), configJsonArray())); + } + +} diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index bc2e515e09..ccfac457aa 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -128,13 +128,16 @@ public Settings settings() { @Override public Set mandatoryOrKeys() { - return ImmutableSet.of("backend_roles", "and_backend_roles", "hosts", "users"); + return ImmutableSet.of("and_backend_roles", "backend_roles", "hosts", "users"); } @Override public Map allowedKeys() { final ImmutableMap.Builder allowedKeys = ImmutableMap.builder(); - if (isCurrentUserAdmin()) allowedKeys.put("reserved", DataType.BOOLEAN); + if (isCurrentUserAdmin()) { + allowedKeys.put("hidden", DataType.BOOLEAN); + allowedKeys.put("reserved", DataType.BOOLEAN); + } return allowedKeys.put("backend_roles", DataType.ARRAY) .put("and_backend_roles", DataType.ARRAY) .put("hosts", DataType.ARRAY) diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java deleted file mode 100644 index dc2ba33e6e..0000000000 --- a/src/test/java/org/opensearch/security/dlic/rest/api/RolesMappingApiTest.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.api; - -import java.util.List; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.hc.core5.http.Header; -import org.apache.http.HttpStatus; -import org.junit.Assert; -import org.junit.Test; - -import org.opensearch.common.settings.Settings; -import org.opensearch.common.xcontent.XContentType; -import org.opensearch.security.DefaultObjectMapper; -import org.opensearch.security.dlic.rest.validation.RequestContentValidator; -import org.opensearch.security.test.helper.file.FileHelper; -import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; - -import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; -import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; - -public class RolesMappingApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; - - protected String getEndpointPrefix() { - return PLUGINS_PREFIX; - } - - public RolesMappingApiTest() { - ENDPOINT = getEndpointPrefix() + "/api"; - } - - @Test - public void testRolesMappingApi() throws Exception { - - setup(); - - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - // create index - setupStarfleetIndex(); - // add user picard, role captains initially maps to - // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); - // TODO: only one doctype allowed for ES6 - // checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - rh.sendAdminCertificate = true; - verifyGetForSuperAdmin(new Header[0]); - rh.sendAdminCertificate = true; - verifyDeleteForSuperAdmin(new Header[0], true); - rh.sendAdminCertificate = true; - verifyPutForSuperAdmin(new Header[0]); - verifyPatchForSuperAdmin(new Header[0]); - // mapping with several backend roles, one of the is captain - deleteAndPutNewMapping(new Header[0], "rolesmapping_backendroles_captains_list.json", true); - checkAllSfAllowed(); - - // mapping with one backend role, captain - deleteAndPutNewMapping(new Header[0], "rolesmapping_backendroles_captains_single.json", true); - checkAllSfAllowed(); - - // mapping with several users, one is picard - deleteAndPutNewMapping(new Header[0], "rolesmapping_users_picard_list.json", true); - checkAllSfAllowed(); - - // just user picard - deleteAndPutNewMapping(new Header[0], "rolesmapping_users_picard_single.json", true); - checkAllSfAllowed(); - - // hosts - deleteAndPutNewMapping(new Header[0], "rolesmapping_hosts_list.json", true); - checkAllSfAllowed(); - - // hosts - deleteAndPutNewMapping(new Header[0], "rolesmapping_hosts_single.json", true); - checkAllSfAllowed(); - - // full settings, access - deleteAndPutNewMapping(new Header[0], "rolesmapping_all_access.json", true); - checkAllSfAllowed(); - - // full settings, no access - deleteAndPutNewMapping(new Header[0], "rolesmapping_all_noaccess.json", true); - checkAllSfForbidden(); - } - - @Test - public void testRolesMappingApiWithFullPermissions() throws Exception { - setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); - rh.sendAdminCertificate = false; - - final Header restApiAdminHeader = encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"); - // create index - setupStarfleetIndex(); - // add user picard, role captains initially maps to - // opendistro_security_role_starfleet_captains and opendistro_security_role_starfleet - addUserWithPassword("picard", "picardpicardpicard", new String[] { "captains" }, HttpStatus.SC_CREATED); - checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picardpicardpicard", "sf", "_doc", 1); - // TODO: only one doctype allowed for ES6 - // checkWriteAccess(HttpStatus.SC_CREATED, "picard", "picard", "sf", "_doc", 1); - - verifyGetForSuperAdmin(new Header[] { restApiAdminHeader }); - verifyDeleteForSuperAdmin(new Header[] { restApiAdminHeader }, false); - verifyPutForSuperAdmin(new Header[] { restApiAdminHeader }); - verifyPatchForSuperAdmin(new Header[] { restApiAdminHeader }); - // mapping with several backend roles, one of the is captain - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_backendroles_captains_list.json", false); - checkAllSfAllowed(); - - // mapping with one backend role, captain - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_backendroles_captains_single.json", true); - checkAllSfAllowed(); - - // mapping with several users, one is picard - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_users_picard_list.json", true); - checkAllSfAllowed(); - - // just user picard - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_users_picard_single.json", true); - checkAllSfAllowed(); - - // hosts - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_hosts_list.json", true); - checkAllSfAllowed(); - - // hosts - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_hosts_single.json", true); - checkAllSfAllowed(); - - // full settings, access - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_all_access.json", true); - checkAllSfAllowed(); - - // full settings, no access - deleteAndPutNewMapping(new Header[] { restApiAdminHeader }, "rolesmapping_all_noaccess.json", true); - checkAllSfForbidden(); - - } - - void verifyGetForSuperAdmin(final Header[] header) throws Exception { - // check rolesmapping exists, old config api - HttpResponse response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // check rolesmapping exists, new API - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // Superadmin should be able to see hidden rolesmapping - Assert.assertTrue(response.getBody().contains("opendistro_security_hidden")); - - // Superadmin should be able to see reserved rolesmapping - Assert.assertTrue(response.getBody().contains("opendistro_security_reserved")); - - // -- GET - // GET opendistro_security_role_starfleet, exists - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals("starfleet", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(0)); - Assert.assertEquals("captains", settings.getAsList("opendistro_security_role_starfleet.backend_roles").get(1)); - Assert.assertEquals("*.starfleetintranet.com", settings.getAsList("opendistro_security_role_starfleet.hosts").get(0)); - Assert.assertEquals("nagilum", settings.getAsList("opendistro_security_role_starfleet.users").get(0)); - - // GET, rolesmapping does not exist - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/nothinghthere", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // GET, new URL endpoint in security - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getContentType(), response.isJsonContentType()); - - // Super admin should be able to describe particular hidden rolemapping - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("\"hidden\":true")); - } - - void verifyDeleteForSuperAdmin(final Header[] header, final boolean useAdminCert) throws Exception { - // Non-existing role - HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/idonotexist", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // read only role - // SuperAdmin can delete read only role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // hidden role - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Assert.assertTrue(response.getBody().contains("'opendistro_security_internal' deleted.")); - - // remove complete role mapping for opendistro_security_role_starfleet_captains - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/configuration/rolesmapping"); - rh.sendAdminCertificate = false; - - // now picard is only in opendistro_security_role_starfleet, which has write access to - // public, but not to _doc - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); - - // TODO: only one doctype allowed for ES6 - // checkWriteAccess(HttpStatus.SC_OK, "picard", "picard", "sf", "_doc", 1); - - // remove also opendistro_security_role_starfleet, poor picard has no mapping left - rh.sendAdminCertificate = useAdminCert; - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - rh.sendAdminCertificate = false; - checkAllSfForbidden(); - } - - void verifyPutForSuperAdmin(final Header[] header) throws Exception { - // put with empty mapping, must fail - HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", "", header); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(RequestContentValidator.ValidationError.PAYLOAD_MANDATORY.message(), settings.get("reason")); - - // put new configuration with invalid payload, must fail - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_not_parseable.json"), - header - ); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.BODY_NOT_PARSEABLE.message(), settings.get("reason")); - - // put new configuration with invalid keys, must fail - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_invalid_keys.json"), - header - ); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.INVALID_CONFIGURATION.message(), settings.get("reason")); - Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("theusers")); - Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("thebackendroles")); - Assert.assertTrue(settings.get(RequestContentValidator.INVALID_KEYS_KEY + ".keys").contains("thehosts")); - - // wrong datatypes - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_backendroles_captains_single_wrong_datatype.json"), - header - ); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); - Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); - Assert.assertTrue(settings.get("hosts") == null); - Assert.assertTrue(settings.get("users") == null); - - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_hosts_single_wrong_datatype.json"), - header - ); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); - Assert.assertTrue(settings.get("hosts").equals("Array expected")); - Assert.assertTrue(settings.get("backend_roles") == null); - Assert.assertTrue(settings.get("users") == null); - - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_users_picard_single_wrong_datatype.json"), - header - ); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.WRONG_DATATYPE.message(), settings.get("reason")); - Assert.assertTrue(settings.get("hosts").equals("Array expected")); - Assert.assertTrue(settings.get("users").equals("Array expected")); - Assert.assertTrue(settings.get("backend_roles").equals("Array expected")); - - // Read only role mapping - // SuperAdmin can add read only roles - mappings - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), - header - ); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - // hidden role, allowed for super admin - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), - header - ); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), - header - ); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - } - - void verifyPatchForSuperAdmin(final Header[] header) throws Exception { - // PATCH on non-existing resource - HttpResponse response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/imnothere", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ \"foo\", \"bar\" ] }]", - header - ); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // PATCH read only resource, must be forbidden - // SuperAdmin can patch read-only resource - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\"] }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH hidden resource, must be not found, can be found by super admin - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_internal", - "[{ \"op\": \"add\", \"path\": \"/a/b/c\", \"value\": [ " + "\"foo\", \"bar\" ] }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH value of hidden flag, must fail with validation error - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", - "[{ \"op\": \"add\", \"path\": \"/hidden\", \"value\": true }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); - - // PATCH - // create a role since PATCH works same as PUT. It is impossible to create role mapping without role - final var securityRoleVulcans = DefaultObjectMapper.objectMapper.createObjectNode() - .set("cluster_permissions", DefaultObjectMapper.objectMapper.createArrayNode().add("cluster:monitor*")) - .toString(); - response = rh.executePutRequest(ENDPOINT + "/roles/opendistro_security_role_vulcans", securityRoleVulcans, header); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", - "[{ \"op\": \"add\", \"path\": \"/backend_roles/-\", \"value\": \"spring\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_vulcans", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - List permissions = settings.getAsList("opendistro_security_role_vulcans.backend_roles"); - Assert.assertNotNull(permissions); - Assert.assertTrue(permissions.contains("spring")); - - // -- PATCH on whole config resource - // PATCH on non-existing resource - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/imnothere/a\", \"value\": [ \"foo\", \"bar\" ] }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH read only resource, must be forbidden - // SuperAdmin can patch read only resource - rh.sendAdminCertificate = true; - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - - // PATCH hidden resource, must be bad request - rh.sendAdminCertificate = true; - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/a\", \"value\": [ \"foo\", \"bar\" ] }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - - // PATCH value of hidden flag, must fail with validation error - rh.sendAdminCertificate = true; - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_vulcans/hidden\", \"value\": true }]", - header - ); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertTrue(response.getBody().matches(".*\"invalid_keys\"\\s*:\\s*\\{\\s*\"keys\"\\s*:\\s*\"hidden\"\\s*\\}.*")); - - // PATCH - // create a role since PATCH works same as PUT. It is impossible to create role mapping without role - final var securityRoleBulknew1 = DefaultObjectMapper.objectMapper.createObjectNode() - .set("cluster_permissions", DefaultObjectMapper.objectMapper.createArrayNode().add("cluster:monitor*")) - .toString(); - response = rh.executePutRequest(ENDPOINT + "/roles/bulknew1", securityRoleBulknew1, header); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/bulknew1\", \"value\": { \"backend_roles\":[\"vulcanadmin\"]} }]", - header - ); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - permissions = settings.getAsList("bulknew1.backend_roles"); - Assert.assertNotNull(permissions); - Assert.assertTrue(permissions.contains("vulcanadmin")); - - // PATCH delete - response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", "[{ \"op\": \"remove\", \"path\": \"/bulknew1\"}]", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/bulknew1", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - } - - private void checkAllSfAllowed() throws Exception { - rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_OK, "picard", "picardpicardpicard", "sf", "_doc", 1); - } - - private void checkAllSfForbidden() throws Exception { - rh.sendAdminCertificate = false; - checkReadAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); - checkWriteAccess(HttpStatus.SC_FORBIDDEN, "picard", "picardpicardpicard", "sf", "_doc", 1); - } - - private HttpResponse deleteAndPutNewMapping(final Header[] header, final String fileName, final boolean useAdminCert) throws Exception { - rh.sendAdminCertificate = useAdminCert; - HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", header); - Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - FileHelper.loadFile("restapi/" + fileName), - header - ); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); - rh.sendAdminCertificate = false; - return response; - } - - @Test - public void testRolesMappingApiForNonSuperAdmin() throws Exception { - - setupWithRestRoles(); - - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = false; - rh.sendHTTPClientCredentials = true; - - verifyNonSuperAdminUser(new Header[0]); - } - - @Test - public void testRolesMappingApiForNonSuperAdminRestApiUser() throws Exception { - setupWithRestRoles(); - rh.sendAdminCertificate = false; - final Header restApiHeader = encodeBasicHeader("test", "test"); - verifyNonSuperAdminUser(new Header[] { restApiHeader }); - } - - void verifyNonSuperAdminUser(final Header[] header) throws Exception { - HttpResponse response; - - // Delete read only roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", header); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Put read only roles mapping - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), - header - ); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Patch single read only roles mapping - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_library", - "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // Patch multiple read only roles mapping - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_role_starfleet_library/description\", \"value\": \"foo\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - - // GET, rolesmapping is hidden, allowed for super admin - response = rh.executeGetRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Delete hidden roles mapping - response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/opendistro_security_internal", header); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Put hidden roles mapping - response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_internal", - FileHelper.loadFile("restapi/rolesmapping_all_access.json"), - header - ); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Patch hidden roles mapping - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping/opendistro_security_internal", - "[{ \"op\": \"add\", \"path\": \"/description\", \"value\": \"foo\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - - // Patch multiple hidden roles mapping - response = rh.executePatchRequest( - ENDPOINT + "/rolesmapping", - "[{ \"op\": \"add\", \"path\": \"/opendistro_security_internal/description\", \"value\": \"foo\" }]", - header - ); - Assert.assertEquals(HttpStatus.SC_NOT_FOUND, response.getStatusCode()); - } - - @Test - public void testChangeRestApiAdminRoleMappingForbidden() throws Exception { - setupWithRestRoles(Settings.builder().put(SECURITY_RESTAPI_ADMIN_ENABLED, true).build()); - rh.sendAdminCertificate = false; - - final var userHeaders = List.of( - encodeBasicHeader("admin", "admin"), - encodeBasicHeader("test", "test"), - encodeBasicHeader("rest_api_admin_user", "rest_api_admin_user"), - encodeBasicHeader("rest_api_admin_rolesmapping", "rest_api_admin_rolesmapping") - ); - - for (final var userHeader : userHeaders) { - // create new mapping for existing group - verifyPutForbidden("rest_api_admin_roles_mapping_test_without_mapping", createUsers("c", "d"), userHeader); - verifyPatchForbidden(createPatchPayload("rest_api_admin_roles_mapping_test_without_mapping", "add"), userHeader); - - // update existing mapping with additional users - verifyPutForbidden("rest_api_admin_roles_mapping_test_with_mapping", createUsers("c", "d"), userHeader); - verifyPatchForbidden(createPatchPayload("rest_api_admin_roles_mapping_test_with_mapping", "replace"), userHeader); - - // delete existing role mapping forbidden - verifyDeleteForbidden("rest_api_admin_roles_mapping_test_with_mapping", userHeader); - verifyPatchForbidden(createPatchPayload("rest_api_admin_roles_mapping_test_with_mapping", "remove"), userHeader); - } - } - - void verifyPutForbidden(final String roleMappingName, final String payload, final Header... header) { - HttpResponse response = rh.executePutRequest(ENDPOINT + "/rolesmapping/" + roleMappingName, payload, header); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - } - - void verifyPatchForbidden(final String payload, final Header... header) { - HttpResponse response = rh.executePatchRequest(ENDPOINT + "/rolesmapping", payload, header); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - } - - void verifyDeleteForbidden(final String roleMappingName, final Header... header) { - HttpResponse response = rh.executeDeleteRequest(ENDPOINT + "/rolesmapping/" + roleMappingName, header); - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); - } - - private String createPatchPayload(final String roleName, final String op) throws JsonProcessingException { - final ArrayNode rootNode = DefaultObjectMapper.objectMapper.createArrayNode(); - final ObjectNode opAddObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); - final ObjectNode clusterPermissionsNode = DefaultObjectMapper.objectMapper.createObjectNode(); - clusterPermissionsNode.set("users", createUsersArray("c", "d")); - if ("add".equals(op)) { - opAddObjectNode.put("op", "add").put("path", "/" + roleName).set("value", clusterPermissionsNode); - rootNode.add(opAddObjectNode); - } - - if ("remove".equals(op)) { - final ObjectNode opRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); - opRemoveObjectNode.put("op", "remove").put("path", "/" + roleName); - rootNode.add(opRemoveObjectNode); - } - - if ("replace".equals(op)) { - final ObjectNode replaceRemoveObjectNode = DefaultObjectMapper.objectMapper.createObjectNode(); - replaceRemoveObjectNode.put("op", "replace").put("path", "/" + roleName + "/users").set("value", createUsersArray("c", "d")); - - rootNode.add(replaceRemoveObjectNode); - } - return DefaultObjectMapper.objectMapper.writeValueAsString(rootNode); - } - - private String createUsers(final String... users) throws JsonProcessingException { - final var o = DefaultObjectMapper.objectMapper.createObjectNode().set("users", createUsersArray("c", "d")); - return DefaultObjectMapper.writeValueAsString(o, false); - } - - private JsonNode createUsersArray(final String... users) { - final ArrayNode usersArray = DefaultObjectMapper.objectMapper.createArrayNode(); - for (final String user : users) { - usersArray.add(user); - } - return usersArray; - } - - @Test - public void checkNullElementsInArray() throws Exception { - setup(); - rh.keystore = "restapi/kirk-keystore.jks"; - rh.sendAdminCertificate = true; - - String body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_users.json"); - HttpResponse response = rh.executePutRequest( - ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", - body, - new Header[0] - ); - Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); - - body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_backend_roles.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); - - body = FileHelper.loadFile("restapi/rolesmapping_null_array_element_hosts.json"); - response = rh.executePutRequest(ENDPOINT + "/rolesmapping/opendistro_security_role_starfleet_captains", body, new Header[0]); - settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); - Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); - } -} diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java b/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java deleted file mode 100644 index dd29b524c1..0000000000 --- a/src/test/java/org/opensearch/security/dlic/rest/api/legacy/LegacyRolesMappingApiTests.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.dlic.rest.api.legacy; - -import org.opensearch.security.dlic.rest.api.RolesMappingApiTest; - -import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX; - -public class LegacyRolesMappingApiTests extends RolesMappingApiTest { - @Override - protected String getEndpointPrefix() { - return LEGACY_OPENDISTRO_PREFIX; - } -}