Skip to content

Commit

Permalink
REST API tests refactoring (Part 4) (opensearch-project#4255)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrey Pleskach <[email protected]>
  • Loading branch information
willyborankin authored May 7, 2024
1 parent 3b94522 commit faac5e3
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 246 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.util.StringJoiner;

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;

import org.opensearch.security.DefaultObjectMapper;
import org.opensearch.security.dlic.rest.api.Endpoint;
import org.opensearch.test.framework.cluster.TestRestClient;

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;

public class ConfigRestApiIntegrationTest extends AbstractApiIntegrationTest {

final static String REST_API_ADMIN_CONFIG_UPDATE = "rest-api-admin-config-update";

static {
clusterSettings.put(SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).put(SECURITY_RESTAPI_ADMIN_ENABLED, true);
testSecurityConfig.withRestAdminUser(REST_ADMIN_USER, allRestAdminPermissions())
.withRestAdminUser(REST_API_ADMIN_CONFIG_UPDATE, restAdminPermission(Endpoint.CONFIG, SECURITY_CONFIG_UPDATE));
}

private String securityConfigPath(final String... path) {
final var fullPath = new StringJoiner("/").add(super.apiPath("securityconfig"));
if (path != null) for (final var p : path)
fullPath.add(p);
return fullPath.toString();
}

@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));
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);
}

@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);
}

@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);
}

void verifyUpdate(final TestRestClient client) throws Exception {
badRequest(() -> client.putJson(securityConfigPath("xxx"), EMPTY_BODY));
verifyNotAllowedMethods(client);

final var configJson = ok(() -> client.get(securityConfigPath())).bodyAsJsonNode();
final var authFailureListeners = DefaultObjectMapper.objectMapper.createObjectNode();
authFailureListeners.set(
"ip_rate_limiting",
DefaultObjectMapper.objectMapper.createObjectNode()
.put("type", "ip")
.put("allowed_tries", 10)
.put("time_window_seconds", 3_600)
.put("block_expiry_seconds", 600)
.put("max_blocked_clients", 100_000)
.put("max_tracked_clients", 100_000)
);
authFailureListeners.set(
"internal_authentication_backend_limiting",
DefaultObjectMapper.objectMapper.createObjectNode()
.put("type", "username")
.put("authentication_backend", "intern")
.put("allowed_tries", 10)
.put("time_window_seconds", 3_600)
.put("block_expiry_seconds", 600)
.put("max_blocked_clients", 100_000)
.put("max_tracked_clients", 100_000)
);
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)));
ok(() -> client.patch(securityConfigPath(), patch(replaceOp("/config/dynamic/hosts_resolver_mode", "other"))));
}

void verifyNotAllowedMethods(final TestRestClient client) throws Exception {
methodNotAllowed(() -> client.postJson(securityConfigPath(), EMPTY_BODY));
methodNotAllowed(() -> client.delete(securityConfigPath()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.opensearch.security.api.PatchPayloadHelper.patch;
import static org.opensearch.security.api.PatchPayloadHelper.replaceOp;

public class DefaultApiAvailabilityIntegrationTest extends AbstractApiIntegrationTest {

Expand Down Expand Up @@ -49,18 +51,7 @@ private void verifySecurityConfigApi(final TestRestClient client) throws Excepti
methodNotAllowed(() -> client.putJson(apiPath("securityconfig"), EMPTY_BODY));
methodNotAllowed(() -> client.postJson(apiPath("securityconfig"), EMPTY_BODY));
methodNotAllowed(() -> client.delete(apiPath("securityconfig")));
forbidden(
() -> client.patch(
apiPath("securityconfig"),
(builder, params) -> builder.startArray()
.startObject()
.field("op", "replace")
.field("path", "/a/b/c")
.field("value", "other")
.endObject()
.endArray()
)
);
forbidden(() -> client.patch(apiPath("securityconfig"), patch(replaceOp("/a/b/c", "other"))));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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.util.Locale;

import org.opensearch.core.xcontent.ToXContentObject;

interface PatchPayloadHelper extends ToXContentObject {

enum Op {
ADD,
REPLACE,
REMOVE;
}

static <T> ToXContentObject addOp(final String path, final T value) {
return operation(Op.ADD, path, value);
}

static <T> ToXContentObject replaceOp(final String path, final T value) {
return operation(Op.REPLACE, path, value);
}

static ToXContentObject removeOp(final String path) {
return operation(Op.REMOVE, path, null);
}

private static <T> ToXContentObject operation(final Op op, final String path, final T value) {
return (builder, params) -> {
final var opPath = path.startsWith("/") ? path : "/" + path;
builder.startObject().field("op", op.name().toLowerCase(Locale.ROOT)).field("path", opPath);
if (value != null) {
if (value instanceof ToXContentObject) {
builder.field("value", (ToXContentObject) value);
} else if (value instanceof String) {
builder.field("value", (String) value);
} else if (value instanceof Boolean) {
builder.field("value", (Boolean) value);
} else {
throw new IllegalArgumentException("Unsupported java type " + value.getClass());
}
}
return builder.endObject();
};
}

static ToXContentObject patch(final ToXContentObject... operations) {
return (builder, params) -> {
builder.startArray();
for (final var o : operations)
o.toXContent(builder, EMPTY_PARAMS);
return builder.endArray();
};
}

}
Loading

0 comments on commit faac5e3

Please sign in to comment.