diff --git a/gateway-provider-security-pac4j/pom.xml b/gateway-provider-security-pac4j/pom.xml index 24316bdd1d..185407554e 100644 --- a/gateway-provider-security-pac4j/pom.xml +++ b/gateway-provider-security-pac4j/pom.xml @@ -106,6 +106,10 @@ org.pac4j pac4j-oidc + + com.nimbusds + oauth2-oidc-sdk + org.pac4j pac4j-saml-opensamlv3 @@ -153,6 +157,33 @@ spring-core + + org.jsoup + jsoup + test + + + org.apache.httpcomponents + httpclient + test + + + org.apache.httpcomponents + httpcore + test + + + org.testcontainers + testcontainers + test + + + annotations + org.jetbrains + + + + org.apache.knox gateway-test-utils diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/MockHttpServletRequest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/MockHttpServletRequest.java index 26e6df3afe..6014efc30a 100644 --- a/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/MockHttpServletRequest.java +++ b/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/MockHttpServletRequest.java @@ -106,4 +106,19 @@ public void setAttribute(String name, Object value) { public Object getAttribute(String name) { return attributes.get(name); } + + @Override + public Map getParameterMap() { + if (parameters == null) { + return java.util.Collections.emptyMap(); + } + + Map map = new java.util.HashMap<>(); + for (Map.Entry entry : parameters.entrySet()) { + map.put(entry.getKey(), new String[]{ entry.getValue() }); + } + + // The Servlet API specifies that this map is generally immutable + return java.util.Collections.unmodifiableMap(map); + } } diff --git a/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderOidcClientTest.java b/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderOidcClientTest.java new file mode 100644 index 0000000000..dec64be766 --- /dev/null +++ b/gateway-provider-security-pac4j/src/test/java/org/apache/knox/gateway/pac4j/Pac4jProviderOidcClientTest.java @@ -0,0 +1,427 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.knox.gateway.pac4j; + +import org.apache.http.Header; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.apache.knox.gateway.audit.api.AuditContext; +import org.apache.knox.gateway.audit.api.AuditService; +import org.apache.knox.gateway.audit.api.Auditor; +import org.apache.knox.gateway.config.GatewayConfig; +import org.apache.knox.gateway.pac4j.filter.Pac4jDispatcherFilter; +import org.apache.knox.gateway.pac4j.filter.Pac4jIdentityAdapter; +import org.apache.knox.gateway.pac4j.session.KnoxSessionStore; +import org.apache.knox.gateway.services.GatewayServices; +import org.apache.knox.gateway.services.ServiceType; +import org.apache.knox.gateway.services.security.AliasService; +import org.apache.knox.gateway.services.security.impl.DefaultCryptoService; +import org.apache.knox.test.category.VerifyTest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.easymock.EasyMock; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.pac4j.core.util.Pac4jConstants; +import org.pac4j.oidc.client.OidcClient; +import org.testcontainers.containers.GenericContainer; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeNoException; + +@Category(VerifyTest.class) +public class Pac4jProviderOidcClientTest { + + private static final Logger LOGGER = LogManager.getLogger(Pac4jProviderOidcClientTest.class); + private static final String LOCALHOST = "localhost"; + private static final String HADOOP_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/sandbox/webhdfs/v1/tmp?op=LISTSTATUS"; + private static final String KNOXSSO_SERVICE_URL = "https://" + LOCALHOST + ":8443/gateway/idp/api/v1/websso"; + private static final String PAC4J_CALLBACK_URL = KNOXSSO_SERVICE_URL; + private static final String ORIGINAL_URL = "originalUrl"; + private static final String CLUSTER_NAME = "knox"; + private static final String PAC4J_PASSWORD = "pwdfortest"; + private static final String CLIENT_CLASS = OidcClient.class.getSimpleName(); + private static final String USERNAME = "JaneUser"; + public static final String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak"; + public static final String KEYCLOAK_VERSION = "21.1.2"; + + private static GenericContainer keycloakContainer; + + @BeforeClass + public static void setUpClass() { + try { + keycloakContainer = new GenericContainer<>(KEYCLOAK_IMAGE + ":"+ KEYCLOAK_VERSION) + .withExposedPorts(8080) + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin") + .withCopyFileToContainer( + org.testcontainers.utility.MountableFile.forClasspathResource("keycloak/realm-export.json"), + "/opt/keycloak/data/import/realm.json") + .withCommand("start-dev", "--import-realm") + .waitingFor(org.testcontainers.containers.wait.strategy.Wait.forHttp("/realms/testrealm/.well-known/openid-configuration").forStatusCode(200)); + + keycloakContainer.start(); + assertTrue(keycloakContainer.isRunning()); + } catch (Exception e) { + assumeNoException(e); + } + } + + @AfterClass + public static void tearDownClass() { + if (keycloakContainer != null) { + keycloakContainer.stop(); + } + } + + @Test + public void testPac4jOidcLoginWithKeycloak() throws Exception { + LOGGER.info("Starting Pac4j OIDC login integration test with Keycloak"); + + ServletContext context = setupKnoxEnvironment(); + Pac4jFilters filters = setupPac4jFilters(context); + ClientCookieStore clientCookieStore = new ClientCookieStore(); + + LOGGER.info("Step 1: Making unauthenticated request to KnoxSSO"); + MockHttpServletResponse redirectResponse = triggerOidcRedirect(filters, clientCookieStore); + clientCookieStore.saveCookies(redirectResponse); + + assertEquals("Should redirect to Keycloak", 302, redirectResponse.getStatus()); + String keycloakAuthUrl = redirectResponse.getHeaders().get("Location").get(0); + + verifyOidcRedirectUri(keycloakAuthUrl); + verifyKnoxSessionCookies(clientCookieStore); + + LOGGER.info("Step 2: Authenticating with Keycloak directly via HTTP client..."); + KeycloakLoginResult loginResult = authenticateWithKeycloak(keycloakAuthUrl, clientCookieStore); + clientCookieStore.saveCookies(loginResult.cookies); + + assertNotNull("Redirect URL should not be null after successful login", loginResult.redirectUrl); + + LOGGER.info("Step 3: Processing OIDC callback from Keycloak"); + MockHttpServletResponse callbackResponse = processOidcCallback(filters, loginResult.redirectUrl, clientCookieStore); + clientCookieStore.saveCookies(callbackResponse); + + assertEquals("Should redirect back to the original service URL", 302, callbackResponse.getStatus()); + String finalRedirectUrl = callbackResponse.getHeaders().get("Location").get(0); + assertEquals(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL, finalRedirectUrl); + + LOGGER.info("Step 4: Executing identity filters with established Knox session"); + MockHttpServletResponse finalResponse = executeKnoxPac4jFilters(filters, clientCookieStore); + + assertEquals(0, finalResponse.getStatus()); // Dispatcher passes through successfully + assertEquals("User identity should be resolved", USERNAME, filters.adapter.getTestIdentifier()); + + LOGGER.info("Identity successfully resolved for user: {}", filters.adapter.getTestIdentifier()); + } + + private ServletContext setupKnoxEnvironment() throws Exception { + final AliasService aliasService = EasyMock.createNiceMock(AliasService.class); + EasyMock.expect(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD, true)) + .andReturn(PAC4J_PASSWORD.toCharArray()).anyTimes(); + EasyMock.expect(aliasService.getPasswordFromAliasForCluster(CLUSTER_NAME, KnoxSessionStore.PAC4J_PASSWORD)) + .andReturn(PAC4J_PASSWORD.toCharArray()).anyTimes(); + EasyMock.replay(aliasService); + + final DefaultCryptoService cryptoService = new DefaultCryptoService(); + cryptoService.setAliasService(aliasService); + + final GatewayServices services = EasyMock.createNiceMock(GatewayServices.class); + EasyMock.expect(services.getService(ServiceType.CRYPTO_SERVICE)).andReturn(cryptoService); + EasyMock.expect(services.getService(ServiceType.ALIAS_SERVICE)).andReturn(aliasService); + EasyMock.replay(services); + + final ServletContext context = EasyMock.createNiceMock(ServletContext.class); + GatewayConfig gatewayConfig = EasyMock.createNiceMock(GatewayConfig.class); + EasyMock.expect(context.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE)).andReturn(gatewayConfig); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(services); + EasyMock.expect(context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE)).andReturn(CLUSTER_NAME); + EasyMock.replay(context); + + return context; + } + + private Pac4jFilters setupPac4jFilters(ServletContext context) throws ServletException { + final FilterConfig config = EasyMock.createNiceMock(FilterConfig.class); + EasyMock.expect(config.getServletContext()).andReturn(context); + EasyMock.expect(config.getInitParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_URL)).andReturn(PAC4J_CALLBACK_URL).anyTimes(); + EasyMock.expect(config.getInitParameter(Pac4jIdentityAdapter.PAC4J_ID_ATTRIBUTE)).andReturn("customIdAttribute"); + EasyMock.expect(config.getInitParameter("clientName")).andReturn("OidcClient").anyTimes(); + EasyMock.expect(config.getInitParameter("oidc.id")).andReturn("test-client-id").anyTimes(); + EasyMock.expect(config.getInitParameter("oidc.secret")).andReturn("test-client-secret").anyTimes(); + + String authServerUrl = String.format(Locale.ROOT, "http://%s:%d", keycloakContainer.getHost(), keycloakContainer.getMappedPort(8080)); + String discoveryUri = authServerUrl + "/realms/testrealm/.well-known/openid-configuration"; + EasyMock.expect(config.getInitParameter("oidc.discoveryUri")).andReturn(discoveryUri).anyTimes(); + EasyMock.expect(config.getInitParameter("oidc.clientAuthenticationMethod")).andReturn("client_secret_basic").anyTimes(); + EasyMock.expect(config.getInitParameter("oidc.preferredJwsAlgorithm")).andReturn("RS256").anyTimes(); + EasyMock.expect(config.getInitParameter("oidc.scope")).andReturn("openid email profile").anyTimes(); + + List initParameterNames = Arrays.asList(Pac4jDispatcherFilter.PAC4J_CALLBACK_URL, + "clientName", "oidc.id", "oidc.secret", "oidc.discoveryUri", "oidc.clientAuthenticationMethod", + "oidc.preferredJwsAlgorithm", "oidc.scope"); + EasyMock.expect(config.getInitParameterNames()).andReturn(Collections.enumeration(initParameterNames)).anyTimes(); + EasyMock.replay(config); + + final Pac4jDispatcherFilter dispatcher = new Pac4jDispatcherFilter(); + dispatcher.init(config); + + final Pac4jIdentityAdapter adapter = new Pac4jIdentityAdapter(); + adapter.init(config); + + Pac4jIdentityAdapter.setAuditor(EasyMock.createNiceMock(Auditor.class)); + final AuditService auditService = EasyMock.createNiceMock(AuditService.class); + EasyMock.expect(auditService.getContext()).andReturn(EasyMock.createNiceMock(AuditContext.class)); + EasyMock.replay(auditService); + Pac4jIdentityAdapter.setAuditService(auditService); + + return new Pac4jFilters(dispatcher, adapter); + } + + private MockHttpServletResponse triggerOidcRedirect(Pac4jFilters filters, ClientCookieStore clientCookieStore) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE, CLUSTER_NAME); + request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL); + request.setCookies(clientCookieStore.getCookies().toArray(new Cookie[0])); + request.setServerName(LOCALHOST); + + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = EasyMock.createNiceMock(FilterChain.class); + filters.dispatcher.doFilter(request, response, filterChain); + + return response; + } + + private KeycloakLoginResult authenticateWithKeycloak(String authUrl, ClientCookieStore clientCookieStore) throws Exception { + BasicCookieStore apacheCookieStore = getBasicCookieStore(clientCookieStore); + + String redirectUrlAfterLogin; + List setCookiesAfterLogin = new ArrayList<>(); + + try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(apacheCookieStore).build()) { + String loginPageHtml; + try (CloseableHttpResponse loginPageResponse = httpClient.execute(new HttpGet(authUrl))) { + loginPageHtml = EntityUtils.toString(loginPageResponse.getEntity()); + } + + Document doc = Jsoup.parse(loginPageHtml); + String postUrl = doc.select("#kc-form-login").attr("action"); + + HttpPost httpPost = new HttpPost(postUrl); + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("username", "user")); + params.add(new BasicNameValuePair("password", "user-password")); + params.add(new BasicNameValuePair("credentialId", "")); + httpPost.setEntity(new UrlEncodedFormEntity(params)); + + try (CloseableHttpResponse loginResponse = httpClient.execute(httpPost)) { + int statusCode = loginResponse.getStatusLine().getStatusCode(); + if (statusCode == 302) { + redirectUrlAfterLogin = loginResponse.getFirstHeader("Location").getValue(); + Header[] headers = loginResponse.getHeaders("Set-Cookie"); + if (headers != null) { + for (Header header : headers) { + setCookiesAfterLogin.add(ClientCookieStore.parseCookieString(header.getValue())); + } + } + } else { + throw new RuntimeException("Keycloak login failed with status: " + statusCode); + } + EntityUtils.consume(loginResponse.getEntity()); + } + } + + return new KeycloakLoginResult(redirectUrlAfterLogin, setCookiesAfterLogin); + } + + private static BasicCookieStore getBasicCookieStore(ClientCookieStore clientCookieStore) { + BasicCookieStore basicCookieStore = new BasicCookieStore(); + + for (Cookie servletCookie : clientCookieStore.getCookies()) { + BasicClientCookie clientCookie = new BasicClientCookie(servletCookie.getName(), servletCookie.getValue()); + clientCookie.setDomain(servletCookie.getDomain() != null ? servletCookie.getDomain() : LOCALHOST); + clientCookie.setPath(servletCookie.getPath() != null ? servletCookie.getPath() : "/"); + basicCookieStore.addCookie(clientCookie); + } + return basicCookieStore; + } + + private MockHttpServletResponse processOidcCallback(Pac4jFilters filters, String callbackUrl, ClientCookieStore clientCookieStore) throws Exception { + assertTrue("Expected Keycloak to redirect back to Knox, but got: " + callbackUrl, callbackUrl.startsWith(KNOXSSO_SERVICE_URL)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE, CLUSTER_NAME); + request.setCookies(clientCookieStore.getCookies().toArray(new Cookie[0])); + request.setRequestURL(callbackUrl); + request.setServerName(LOCALHOST); + + String state = getQueryParameter(callbackUrl, "state"); + String code = getQueryParameter(callbackUrl, "code"); + String sessionState = getQueryParameter(callbackUrl, "session_state"); + + request.addParameter(Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER, "true"); + request.addParameter(Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER, CLIENT_CLASS); + request.addParameter("state", state); + request.addParameter("session_state", sessionState); + request.addParameter("code", code); + + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = EasyMock.createNiceMock(FilterChain.class); + filters.dispatcher.doFilter(request, response, filterChain); + + return response; + } + + private MockHttpServletResponse executeKnoxPac4jFilters(Pac4jFilters filters, ClientCookieStore clientCookieStore) throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE, CLUSTER_NAME); + request.setCookies(clientCookieStore.getCookies().toArray(new Cookie[0])); + request.setRequestURL(KNOXSSO_SERVICE_URL + "?" + ORIGINAL_URL + "=" + HADOOP_SERVICE_URL); + request.setServerName(LOCALHOST); + + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = EasyMock.createNiceMock(FilterChain.class); + + filters.dispatcher.doFilter(request, response, filterChain); + filters.adapter.doFilter(request, response, filterChain); + + return response; + } + + private void verifyOidcRedirectUri(String locationHeader) throws Exception { + String redirectUriParam = getQueryParameter(locationHeader, "redirect_uri"); + + String redirectUri = URLDecoder.decode(redirectUriParam, StandardCharsets.UTF_8.name()); + String expectedRedirectUri = PAC4J_CALLBACK_URL + "?" + + Pac4jDispatcherFilter.PAC4J_CALLBACK_PARAMETER + "=true&" + + Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER + "=" + CLIENT_CLASS; + assertEquals(expectedRedirectUri, redirectUri); + } + + private void verifyKnoxSessionCookies(ClientCookieStore clientCookieStore) { + boolean hasRequestedUrlCookie = clientCookieStore.getCookies().stream() + .anyMatch(c -> c.getName().equals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.REQUESTED_URL)); + assertTrue("Must contain PAC4J requested URL cookie", hasRequestedUrlCookie); + + long csrfTokenCount = clientCookieStore.getCookies().stream() + .filter(c -> c.getName().equals(KnoxSessionStore.PAC4J_SESSION_PREFIX + Pac4jConstants.CSRF_TOKEN)).count(); + assertEquals("Must contain exactly one PAC4J CSRF token cookie", 1, csrfTokenCount); + } + + private String getQueryParameter(String url, String paramName) throws Exception { + List params = URLEncodedUtils.parse(new URI(url), StandardCharsets.UTF_8); + return params.stream() + .filter(p -> paramName.equals(p.getName())) + .findFirst() + .map(NameValuePair::getValue) + .orElse(null); + } + + private static class Pac4jFilters { + final Pac4jDispatcherFilter dispatcher; + final Pac4jIdentityAdapter adapter; + + Pac4jFilters(Pac4jDispatcherFilter dispatcher, Pac4jIdentityAdapter adapter) { + this.dispatcher = dispatcher; + this.adapter = adapter; + } + } + + private static class KeycloakLoginResult { + final String redirectUrl; + final List cookies; + + KeycloakLoginResult(String redirectUrl, List cookies) { + this.redirectUrl = redirectUrl; + this.cookies = cookies; + } + } + + /** + * Stores and updates cookies across requests to maintain state. + */ + private static class ClientCookieStore { + private final Map cookies = new HashMap<>(); + + public List getCookies() { + return new ArrayList<>(cookies.values()); + } + + public void saveCookies(MockHttpServletResponse response) { + if (response.getCookies() != null) { + for (Cookie c : response.getCookies()) { + cookies.put(c.getName(), c); + } + } + List headers = response.getHeaders().get("Set-Cookie"); + if (headers != null) { + for (String header : headers) { + Cookie c = parseCookieString(header); + cookies.put(c.getName(), c); + } + } + } + + public void saveCookies(List newCookies) { + if (newCookies != null) { + for (Cookie c : newCookies) { + cookies.put(c.getName(), c); + } + } + } + + public static Cookie parseCookieString(String setCookieHeader) { + String[] cookieParts = setCookieHeader.split(";"); + String[] nameValuePairs = cookieParts[0].trim().split("=", 2); + return new Cookie(nameValuePairs[0].trim(), nameValuePairs[1].trim()); + } + } + +} diff --git a/gateway-provider-security-pac4j/src/test/resources/keycloak/realm-export.json b/gateway-provider-security-pac4j/src/test/resources/keycloak/realm-export.json new file mode 100644 index 0000000000..f4ad7cc76c --- /dev/null +++ b/gateway-provider-security-pac4j/src/test/resources/keycloak/realm-export.json @@ -0,0 +1,189 @@ +{ + "realm": "testrealm", + "enabled": true, + "roles": { + "realm": [ + { "name": "admin", "description": "Administrative privileges" }, + { "name": "user", "description": "Standard user privileges" } + ] + }, + "groups": [ + { + "name": "admin", + "path": "/admin", + "realmRoles": ["admin"] + }, + { + "name": "users", + "path": "/users", + "realmRoles": ["user"] + } + ], + "clientScopes": [ + { + "name": "email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "name": "profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "config": { + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + } + ] + }, + { + "name": "custom-identity", + "protocol": "openid-connect", + "protocolMappers": [ + { + "name": "map-custom-id", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "config": { + "user.attribute": "customIdAttribute", + "claim.name": "customIdAttribute", + "jsonType.label": "String", + "id.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "name": "group-mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "config": { + "full.path": "false", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "custom-identity" + ], + "clients": [ + { + "clientId": "test-client-id", + "enabled": true, + "protocol": "openid-connect", + "publicClient": false, + "secret": "test-client-secret", + "redirectUris": ["https://localhost:8443/gateway/*"], + "webOrigins": ["https://localhost:8443/gateway/*"], + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "defaultClientScopes": [ + "email", + "profile", + "custom-identity" + ] + } + ], + "users": [ + { + "username": "admin", + "email": "admin@example.com", + "firstName": "Joe", + "lastName": "Admin", + "enabled": true, + "credentials": [{ "type": "password", "value": "admin-password", "temporary": false }], + "attributes": { "customIdAttribute": ["JoeAdmin"] }, + "groups": ["admin"] + }, + { + "username": "user", + "email": "user@example.com", + "firstName": "Jane", + "lastName": "User", + "enabled": true, + "credentials": [{ "type": "password", "value": "user-password", "temporary": false }], + "attributes": { "customIdAttribute": ["JaneUser"] }, + "groups": ["users"] + } + ] +} diff --git a/gateway-provider-security-pac4j/src/test/resources/log4j2-test.xml b/gateway-provider-security-pac4j/src/test/resources/log4j2-test.xml new file mode 100644 index 0000000000..b91f3c0dfe --- /dev/null +++ b/gateway-provider-security-pac4j/src/test/resources/log4j2-test.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 8fed262a91..1ba43d1584 100644 --- a/pom.xml +++ b/pom.xml @@ -243,6 +243,7 @@ 2.10.8 2.9.0 2.5.2 + 1.22.2 4.13.2 1.9.10 1.5 @@ -265,6 +266,7 @@ 42.4.4 8.0.28 3.3.0 + 9.13 3.6.0 3.25.5 2.0.9 @@ -287,7 +289,7 @@ 1.6.2 3.1.7 1.2.5 - 1.15.1 + 1.16.3 2.4.0-b180830.0438 2.4.1 6.4.0 @@ -1430,6 +1432,11 @@ lang-tag ${lang-tag.version} + + com.nimbusds + oauth2-oidc-sdk + ${oauth2-oidc-sdk.version} + net.minidev @@ -2452,6 +2459,12 @@ org.pac4j pac4j-oidc ${pac4j.version} + + + com.nimbusds + oauth2-oidc-sdk + + org.pac4j @@ -2688,6 +2701,12 @@ ${hamcrest.version} test + + org.jsoup + jsoup + ${jsoup.version} + test + uk.co.datumedge hamcrest-json