Skip to content

Commit

Permalink
test attempt
Browse files Browse the repository at this point in the history
Signed-off-by: David Kral <[email protected]>
  • Loading branch information
Verdent committed Nov 28, 2024
1 parent 86b03e6 commit 9d4e03f
Show file tree
Hide file tree
Showing 9 changed files with 248 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,9 @@ public Builder config(Config config) {
config.get("proxy-port").asInt().ifPresent(this::proxyPort);
config.get("relative-uris").asBoolean().ifPresent(this::relativeUris);

//flow
config.get("flow").as(OidcFlowType.class).ifPresent(this::flow);

// token handling
config.get("query-param-use").asBoolean().ifPresent(this::useParam);
config.get("query-param-name").asString().ifPresent(this::paramName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import static io.helidon.security.providers.oidc.common.spi.TenantConfigFinder.DEFAULT_TENANT_ID;

class AuthorizationCodeFlow implements OidcFlow {
private static final System.Logger LOGGER = System.getLogger(OidcProvider.class.getName());
private static final System.Logger LOGGER = System.getLogger(AuthorizationCodeFlow.class.getName());

private final OidcConfig oidcConfig;
private final OidcOutboundConfig outboundConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import java.util.Map;

import io.helidon.common.parameters.Parameters;
import io.helidon.http.HeaderNames;
import io.helidon.http.Status;
import io.helidon.security.AuthenticationResponse;
import io.helidon.security.EndpointConfig;
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.ProviderRequest;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.SecurityResponse;
import io.helidon.security.providers.oidc.common.OidcConfig;
import io.helidon.security.util.TokenHandler;
import io.helidon.webclient.api.HttpClientRequest;
Expand All @@ -20,13 +22,16 @@
import jakarta.json.JsonValue;

class ClientCredentialsFlow implements OidcFlow {
private static final System.Logger LOGGER = System.getLogger(ClientCredentialsFlow.class.getName());

private final OidcConfig oidcConfig;
private final OidcOutboundConfig oidcOutboundConfig;
private final boolean optional;

ClientCredentialsFlow(OidcConfig oidcConfig, OidcOutboundConfig oidcOutboundConfig) {
ClientCredentialsFlow(OidcProvider.Builder builder, OidcConfig oidcConfig, OidcOutboundConfig oidcOutboundConfig) {
this.oidcConfig = oidcConfig;
this.oidcOutboundConfig = oidcOutboundConfig;
this.optional = builder.optional();
}

@Override
Expand All @@ -38,8 +43,6 @@ public AuthenticationResponse authenticate(ProviderRequest providerRequest) {
public OutboundSecurityResponse outboundSecurity(ProviderRequest providerRequest,
SecurityEnvironment outboundEnv,
EndpointConfig outboundConfig) {


OidcOutboundConfig.OidcOutboundTarget target = oidcOutboundConfig.findTarget(outboundEnv);
boolean enabled = target.propagate();
if (enabled) {
Expand All @@ -65,8 +68,17 @@ public OutboundSecurityResponse outboundSecurity(ProviderRequest providerRequest
target.tokenHandler().header(headers, accessToken);
return OutboundSecurityResponse.withHeaders(headers);
} else {

return OutboundSecurityResponse.builder()
.status(SecurityResponse.SecurityStatus.FAILURE)
.description("Could not obtain access token from the identity server")
.build();
}
} catch (Exception e) {
return OutboundSecurityResponse.builder()
.status(SecurityResponse.SecurityStatus.FAILURE)
.description("An error occurred while obtaining access token from the identity server")
.throwable(e)
.build();
}
}
return OutboundSecurityResponse.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,26 @@

package io.helidon.security.providers.oidc;

import java.lang.System.Logger.Level;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import io.helidon.common.HelidonServiceLoader;
import io.helidon.common.config.Config;
import io.helidon.common.configurable.LruCache;
import io.helidon.config.metadata.Configured;
import io.helidon.config.metadata.ConfiguredOption;
import io.helidon.security.AuthenticationResponse;
import io.helidon.security.EndpointConfig;
import io.helidon.security.OutboundSecurityResponse;
import io.helidon.security.ProviderRequest;
import io.helidon.security.SecurityEnvironment;
import io.helidon.security.Subject;
import io.helidon.security.abac.scope.ScopeValidator;
import io.helidon.security.providers.common.OutboundConfig;
import io.helidon.security.providers.common.OutboundTarget;
import io.helidon.security.providers.common.TokenCredential;
import io.helidon.security.providers.oidc.common.OidcConfig;
import io.helidon.security.providers.oidc.common.OidcFlowType;
import io.helidon.security.providers.oidc.common.Tenant;
import io.helidon.security.providers.oidc.common.TenantConfig;
import io.helidon.security.providers.oidc.common.spi.TenantConfigFinder;
import io.helidon.security.providers.oidc.common.spi.TenantConfigProvider;
import io.helidon.security.providers.oidc.common.spi.TenantIdFinder;
Expand All @@ -57,8 +45,6 @@
import io.helidon.security.spi.SecurityProvider;
import io.helidon.security.util.TokenHandler;

import static io.helidon.security.providers.oidc.common.spi.TenantConfigFinder.DEFAULT_TENANT_ID;

/**
* Open ID Connect authentication provider.
*
Expand All @@ -75,15 +61,14 @@
public final class OidcProvider implements AuthenticationProvider, OutboundSecurityProvider {

private final OidcFlow flow;
private final OidcConfig oidcConfig;
private final OidcOutboundConfig outboundConfig;
private final boolean propagate;

private OidcProvider(Builder builder, OidcOutboundConfig oidcOutboundConfig) {
this.oidcConfig = builder.oidcConfig;
this.outboundConfig = oidcOutboundConfig;
OidcConfig oidcConfig = builder.oidcConfig;
if (oidcConfig.flow() == OidcFlowType.CREDENTIALS) {
this.flow = new ClientCredentialsFlow(oidcConfig, oidcOutboundConfig);
this.flow = new ClientCredentialsFlow(builder, oidcConfig, oidcOutboundConfig);
} else {
this.flow = new AuthorizationCodeFlow(builder, oidcConfig, oidcOutboundConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import jakarta.ws.rs.core.MediaType;

import io.helidon.security.annotations.Authenticated;
import io.helidon.webclient.api.WebClient;

/**
* Test resource.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2023, 2024 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.tests.integration.oidc;

import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.Response;
import org.junit.jupiter.api.Test;

import static io.helidon.tests.integration.oidc.TestResource.EXPECTED_POST_LOGOUT_TEST_MESSAGE;
import static io.helidon.tests.integration.oidc.TestResource.EXPECTED_TEST_MESSAGE;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;

class ClientCredentialsIT extends CommonLoginBase {

@Test
public void testSuccessfulLogin(WebTarget webTarget) {
String formUri;
//greet endpoint is protected, and we need to get JWT token out of the Keycloak. We will get redirected to the Keycloak.
try (Response response = client.target(webTarget.getUri())
.path("/test")
.request()
.header("helidon-tenant", "my-super-tenant")
.get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
//We need to get form URI out of the HTML
formUri = getRequestUri(response.readEntity(String.class));
}

//Sending authentication to the Keycloak and getting redirected back to the running Helidon app.
Entity<Form> form = Entity.form(new Form().param("username", "userthree")
.param("password", "12345")
.param("credentialId", ""));
try (Response response = client.target(formUri).request().post(form)) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_TEST_MESSAGE));
}

//next request should have cookie set, and we do not need to authenticate again
try (Response response = client.target(webTarget.getUri()).path("/test").request().get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_TEST_MESSAGE));
}
}

@Test
public void testFallbackToDefaultIfTenantNotFound(WebTarget webTarget) {
String formUri;

//greet endpoint is protected, and we need to get JWT token out of the Keycloak. We will get redirected to the Keycloak.
try (Response response = client.target(webTarget.getUri()).path("/test")
.request()
.header("helidon-tenant", "nonexistent")
.get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
//We need to get form URI out of the HTML
formUri = getRequestUri(response.readEntity(String.class));
}

//This user is defined in realm Test 1 which uses tenant "localhost",
//tenant which does not exist falls back to the default configuration.
//Default tenant uses realm Test 2, and it only has defined userone and usertwo.
Entity<Form> form = Entity.form(new Form().param("username", "userthree")
.param("password", "12345")
.param("credentialId", ""));
try (Response response = client.target(formUri).request().post(form)) {
//Keycloak for some reason sends 200 OK even if login failed
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
//Now we should not have our target endpoint response since authentication failed
String content = response.readEntity(String.class);
assertThat(content, not(EXPECTED_TEST_MESSAGE));
//We need to update form uri, since it has changed due to unsuccessful login
formUri = getRequestUri(content);
}

//Sending authentication to the Keycloak and getting redirected back to the running Helidon app.
form = Entity.form(new Form().param("username", "userone")
.param("password", "12345")
.param("credentialId", ""));
try (Response response = client.target(formUri).request().post(form)) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_TEST_MESSAGE));
}
}

@Test
public void testDefaultTenantUsage(WebTarget webTarget) {
String formUri;

//greet endpoint is protected, and we need to get JWT token out of the Keycloak. We will get redirected to the Keycloak.
try (Response response = client.target(webTarget.getUri()).path("/test")
.request()
.get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
//We need to get form URI out of the HTML
formUri = getRequestUri(response.readEntity(String.class));
}

//Sending authentication to the Keycloak and getting redirected back to the running Helidon app.
Entity<Form> form = Entity.form(new Form().param("username", "userone")
.param("password", "12345")
.param("credentialId", ""));
try (Response response = client.target(formUri).request().post(form)) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_TEST_MESSAGE));
}
}

@Test
public void testLogoutFunctionality(WebTarget webTarget) {
String formUri;

//greet endpoint is protected, and we need to get JWT token out of the Keycloak. We will get redirected to the Keycloak.
try (Response response = client.target(webTarget.getUri()).path("/test")
.request()
.get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
//We need to get form URI out of the HTML
formUri = getRequestUri(response.readEntity(String.class));
}

//Sending authentication to the Keycloak and getting redirected back to the running Helidon app.
Entity<Form> form = Entity.form(new Form().param("username", "userone")
.param("password", "12345")
.param("credentialId", ""));
try (Response response = client.target(formUri).request().post(form)) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_TEST_MESSAGE));
}

try (Response response = client.target(webTarget.getUri()).path("/oidc/logout")
.request()
.get()) {
assertThat(response.getStatus(), is(Response.Status.OK.getStatusCode()));
assertThat(response.readEntity(String.class), is(EXPECTED_POST_LOGOUT_TEST_MESSAGE));
}

try (Response response = client.target(webTarget.getUri()).path("/oidc/logout")
.request()
.get()) {
//There should be not token present among the cookies since it was cleared by the previous call
assertThat(response.getStatus(), is(Response.Status.FORBIDDEN.getStatusCode()));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.jupiter.api.Test;

import static io.helidon.tests.integration.oidc.TestResource.EXPECTED_POST_LOGOUT_TEST_MESSAGE;
Expand Down
14 changes: 14 additions & 0 deletions tests/integration/oidc/src/test/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,17 @@ security:
client-id: "clientTwo"
client-secret: "jjWNrK4EVQCuZYsxUfC4AZ7y6StS6Jmy"
identity-uri: "http://localhost:8080/realms/test2/"


credentials-flow:
security:
providers:
- abac:
- oidc:
flow: credentials
identity-uri: "http://localhost:8888/realms/master/"
client-id: "client-credentials-test"
client-secret: "tLQ9lKhNoJHRtwnp7OA1IoaMJa6XWn4b"
outbound:
- name: "include-token"
hosts: ["*"]
Loading

0 comments on commit 9d4e03f

Please sign in to comment.