diff --git a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java index 4b2a5a4e207..f2b87d68498 100644 --- a/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java +++ b/metrics/api/src/main/java/io/helidon/metrics/api/MetricsConfigBlueprint.java @@ -134,9 +134,17 @@ static List createTags(String pairs) { * * @return if metrics are configured to be authorized */ - @ConfiguredOption("true") + @ConfiguredOption boolean permitAll(); + /** + * Hints for role names the user is expected to be in. + * + * @return list of hints + */ + @ConfiguredOption + List roles(); + /** * Key performance indicator metrics settings. * diff --git a/webserver/service-common/pom.xml b/webserver/service-common/pom.xml index e4947c52bf5..dd998494748 100644 --- a/webserver/service-common/pom.xml +++ b/webserver/service-common/pom.xml @@ -51,6 +51,11 @@ hamcrest-all test + + io.helidon.webserver.testing.junit5 + helidon-webserver-testing-junit5 + test + diff --git a/webserver/service-common/src/main/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureSupport.java b/webserver/service-common/src/main/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureSupport.java index 9f4e61e5ef2..87e24eddc86 100644 --- a/webserver/service-common/src/main/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureSupport.java +++ b/webserver/service-common/src/main/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureSupport.java @@ -26,7 +26,6 @@ * Common base implementation for {@link HelidonFeatureSupport} with secured endpoints. */ public abstract class HelidonSecureFeatureSupport extends HelidonFeatureSupport { - private static final String PERMIT_ALL_KEY = "permit-all"; private final boolean permitAll; private final String[] roles; @@ -60,9 +59,12 @@ protected Builder(String defaultContext) { @Override public B config(Config config) { super.config(config); - config.get(PERMIT_ALL_KEY) + config.get("permit-all") .asBoolean() .ifPresent(this::permitAll); + config.get("roles") + .asList(String.class) + .ifPresent(roles::addAll); return identity(); } diff --git a/webserver/service-common/src/test/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureTest.java b/webserver/service-common/src/test/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureTest.java new file mode 100644 index 00000000000..0d475ada36a --- /dev/null +++ b/webserver/service-common/src/test/java/io/helidon/webserver/servicecommon/HelidonSecureFeatureTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.webserver.servicecommon; + +import java.net.URI; +import java.util.Optional; + +import io.helidon.config.Config; +import io.helidon.webclient.http1.Http1Client; +import io.helidon.webclient.http1.Http1ClientResponse; +import io.helidon.webserver.WebServer; +import io.helidon.webserver.http.HttpRouting; +import io.helidon.webserver.http.HttpService; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class HelidonSecureFeatureTest { + private static final String ENDPOINT_UNAUTHORIZED_DEFAULT = "unauthorized-default"; + private static final String ENDPOINT_AUTHORIZED_DEFAULT = "authorized-default"; + private static final String ENDPOINT_UNAUTHORIZED_CONFIG = "unauthorized-config"; + private static final String ENDPOINT_AUTHORIZED_CONFIG = "authorized-config"; + private static final String ENDPOINT_AUTHORIZED_BUILDER = "authorized-builder"; + + private static WebServer server; + private static Http1Client client; + + @BeforeAll + static void setup() { + server = WebServer.builder() + .routing(HttpRouting.builder() + .addFeature(HelidonSecureFeatureSupportImpl.createUnauthorizedDefault()) + .addFeature(HelidonSecureFeatureSupportImpl.createAuthorizedDefault()) + .addFeature(HelidonSecureFeatureSupportImpl.createUnauthorizedConfig()) + .addFeature(HelidonSecureFeatureSupportImpl.createAuthorizedConfig()) + .addFeature(HelidonSecureFeatureSupportImpl.createAuthorizedBuilder()) + .build()) + .port(-1) + .build() + .start(); + client = Http1Client.builder() + .baseUri(URI.create("http://127.0.0.1:" + server.port())) + .build(); + } + + @AfterAll + static void close() { + if (server != null) { + server.stop(); + } + } + + @Test + void testAuthorization() { + runTestAuthorization(ENDPOINT_AUTHORIZED_BUILDER, true); + runTestAuthorization(ENDPOINT_AUTHORIZED_CONFIG, true); + runTestAuthorization(ENDPOINT_AUTHORIZED_DEFAULT, true); + runTestAuthorization(ENDPOINT_UNAUTHORIZED_CONFIG, false); + runTestAuthorization(ENDPOINT_UNAUTHORIZED_DEFAULT, false); + } + + void runTestAuthorization(String path, boolean authorized) { + try (Http1ClientResponse response = client.get(path).request()) { + assertThat(response.status().code(), is(authorized ? 200 : 403)); + } + } + + private static class HelidonSecureFeatureSupportImpl extends HelidonSecureFeatureSupport { + private static final System.Logger LOGGER = System.getLogger(HelidonSecureFeatureSupportImpl.class.getName()); + private static final String ROLES = "roles"; + + HelidonSecureFeatureSupportImpl(Builder builder) { + super(LOGGER, builder, "Helidon Secure Feature"); + } + + static HelidonSecureFeatureSupportImpl createAuthorizedDefault() { + return builder(ENDPOINT_AUTHORIZED_DEFAULT) + .build(); + } + + static HelidonSecureFeatureSupportImpl createUnauthorizedDefault() { + return builder(ENDPOINT_UNAUTHORIZED_DEFAULT) + .addRoleHint("observer") + .build(); + } + + static HelidonSecureFeatureSupportImpl createAuthorizedConfig() { + return builder(ENDPOINT_AUTHORIZED_CONFIG) + .config(Config.create()) + .build(); + } + + static HelidonSecureFeatureSupportImpl createUnauthorizedConfig() { + System.setProperty(ROLES, "observe"); + Builder builder = builder(ENDPOINT_UNAUTHORIZED_CONFIG) + .config(Config.create()); + System.clearProperty(ROLES); + return builder.build(); + } + + static HelidonSecureFeatureSupportImpl createAuthorizedBuilder() { + return builder(ENDPOINT_AUTHORIZED_BUILDER) + .permitAll(true) + .addRoleHint("observe") + .build(); + } + + private static Builder builder(String path) { + return new Builder(path); + } + + @Override + public Optional service() { + return Optional.of(rules -> { + authorizeRules(rules); + rules.get(((req, res) -> res.send("Accessed"))); + }); + } + + static class Builder extends HelidonSecureFeatureSupport.Builder { + Builder(String defaultContext) { + super(defaultContext); + } + + @Override + public HelidonSecureFeatureSupportImpl build() { + return new HelidonSecureFeatureSupportImpl(this); + } + } + } +}