From ed53aa32ff8fab61563fe48d7541084ed0e615b6 Mon Sep 17 00:00:00 2001 From: Patrik Dudits Date: Mon, 15 Aug 2022 17:40:20 +0200 Subject: [PATCH] FISH-5725: Test for reading groups from Id token only --- openid-standalone-it/pom.xml | 12 +++ .../openid/idp/AbstractIdProvider.java | 6 +- .../openid/idp/NaiveCookieManager.java | 82 +++++++++++++++++++ .../payara/security/openid/adfs/AdfsAuth.java | 4 +- .../security/openid/adfs/AdfsEmulation.java | 6 +- .../security/openid/adfs/AdfsEmulationIT.java | 30 ++++++- .../security/openid/adfs/OpenIdCallback.java | 78 ++++++++++++++++++ 7 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 openid-standalone-it/src/main/java/fish/payara/security/openid/idp/NaiveCookieManager.java create mode 100644 openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/OpenIdCallback.java diff --git a/openid-standalone-it/pom.xml b/openid-standalone-it/pom.xml index 75521c4c..7298eae4 100644 --- a/openid-standalone-it/pom.xml +++ b/openid-standalone-it/pom.xml @@ -86,6 +86,12 @@ + + fish.payara.security.connectors + security-connectors-api + ${project.version} + provided + fish.payara.security.connectors openid-standalone @@ -178,6 +184,12 @@ arquillian-payara-server-remote test + + + jakarta.xml.bind + jakarta.xml.bind-api + runtime + diff --git a/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/AbstractIdProvider.java b/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/AbstractIdProvider.java index ba6a4e7d..de8d62a2 100644 --- a/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/AbstractIdProvider.java +++ b/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/AbstractIdProvider.java @@ -180,9 +180,8 @@ public Response authEndpoint(@BeanParam AuthRequest authRequest) throws URISynta @Path("token") @POST @Consumes(APPLICATION_FORM_URLENCODED) - public Response tokenEndpoint(@BeanParam TokenRequest tokenRequest, MultivaluedMap allParams) { - //TokenRequest tokenRequest = new TokenRequest(allParams); - tokenRequest.allParams = allParams; + public Response tokenEndpoint(MultivaluedMap allParams) { + TokenRequest tokenRequest = new TokenRequest(allParams); try { Token result; @@ -434,6 +433,7 @@ public TokenRequest() { public TokenRequest(MultivaluedMap allParams) { this.allParams = allParams; + code = allParams.getFirst(CODE); clientId = allParams.getFirst(CLIENT_ID); clientSecret = allParams.getFirst(CLIENT_SECRET); grantType = allParams.getFirst(GRANT_TYPE); diff --git a/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/NaiveCookieManager.java b/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/NaiveCookieManager.java new file mode 100644 index 00000000..800e111e --- /dev/null +++ b/openid-standalone-it/src/main/java/fish/payara/security/openid/idp/NaiveCookieManager.java @@ -0,0 +1,82 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2022 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + */ + +package fish.payara.security.openid.idp; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.ClientResponseContext; +import javax.ws.rs.client.ClientResponseFilter; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.NewCookie; + +public class NaiveCookieManager implements ClientRequestFilter, ClientResponseFilter { + private static Map cookies = new ConcurrentHashMap<>(); + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + for (Cookie cookie : cookies.values()) { + if (matchesDomain(requestContext.getUri(), cookie)) { + requestContext.getHeaders().add("Cookie", cookie.toString()); + } + } + } + + private boolean matchesDomain(URI uri, Cookie cookie) { + // let's not think about domains right now + return uri.getPath().startsWith(cookie.getPath()); + } + + + @Override + public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException { + for (NewCookie cookie : responseContext.getCookies().values()) { + Cookie c = new Cookie(cookie.getName(), cookie.getValue(), cookie.getPath(), cookie.getDomain(), cookie.getVersion()); + cookies.put(c.getDomain() + "/" + c.getName(), c); + } + } +} diff --git a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsAuth.java b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsAuth.java index 1a07c669..6d18b5d9 100644 --- a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsAuth.java +++ b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsAuth.java @@ -59,10 +59,10 @@ clientId = "test_client", clientSecret = "test_client", providerURI = "#{urlExtractor.providerUrl}", - useSession = false, providerMetadata = @OpenIdProviderMetadata( accessTokenIssuer = "http://someone-else" - ) + ), + userClaimsFromIDToken = true ) @Path("client") @RolesAllowed("authenticated") diff --git a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulation.java b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulation.java index c63e07fe..9321feec 100644 --- a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulation.java +++ b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulation.java @@ -43,6 +43,7 @@ package fish.payara.security.openid.adfs; import java.net.URI; +import java.util.Arrays; import java.util.Date; import javax.json.JsonObject; @@ -93,7 +94,8 @@ protected Token exchangeToken(TokenRequest request) throws AuthException { @Override protected Token exchangeToken(AuthCode code) throws AuthException { Token result = new Token(); - result.setIdToken(result.claimsFor(code, providerRoot(uriInfo), "test_object")); + result.setIdToken(result.claimsFor(code, providerRoot(uriInfo).resolve("idp/"), "test_object").claim("groups", Arrays.asList("authenticated", + "code_exchange"))); result.setAccessToken(result.claimsFor(code, URI.create("http://someone-else"), "test_object")); return result; } @@ -110,6 +112,6 @@ protected JWKSet getKeyset() { @Override protected JsonObject userInfo(Token token) { - throw new NotAuthorizedException("ADFS throws 401 here", (Response) null); + throw new NotAuthorizedException("ADFS throws 401 here", Response.status(401).build()); } } diff --git a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulationIT.java b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulationIT.java index 6e8f2e64..5e816e4e 100644 --- a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulationIT.java +++ b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/AdfsEmulationIT.java @@ -45,14 +45,19 @@ import java.io.IOException; import java.net.URI; +import javax.json.Json; +import javax.json.JsonArray; import javax.json.JsonObject; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Form; +import javax.ws.rs.core.Response; +import fish.payara.arquillian.jersey.client.ClientProperties; import fish.payara.security.openid.idp.LogExceptionOnServerSide; +import fish.payara.security.openid.idp.NaiveCookieManager; import fish.payara.security.openid.idp.OpenIdDeployment; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit5.ArquillianExtension; @@ -69,7 +74,7 @@ public class AdfsEmulationIT { @Deployment public static WebArchive deployment() { return OpenIdDeployment.withAbstractProvider().addClasses(JaxrsApplication.class, AdfsEmulation.class, AdfsAuth.class, - AccessTokenRoleMapping.class, UrlExtractor.class); + AccessTokenRoleMapping.class, UrlExtractor.class, OpenIdCallback.class, NaiveCookieManager.class); } @ArquillianResource @@ -85,4 +90,27 @@ public void accessTokenGetsAccepted() throws IOException { String myself = base.path("client").request().header("Authorization", "Bearer " + accessToken).get(String.class); assertEquals("test_subject", myself); } + + @Test + public void userInfoEndpointIsNotTouched() { + Client client = ClientBuilder.newClient().register(new NaiveCookieManager()).property(ClientProperties.FOLLOW_REDIRECTS, false); + + WebTarget base = client.target(baseUri); + // this request redirects takes client to code authorization endpoint, and gets redirected to openid callback + // we need to manually follow these redirects otherwise our naive cookie manager will not collect relevant cookies + // to identify ourselves when we land back at callback + + // client redirects us to idp code + Response response = base.path("client").request().get(); + assertEquals(Response.Status.Family.REDIRECTION, response.getStatusInfo().getFamily()); + + // code redirects to OAuth callback + response = client.target(response.getLocation()).request().get(); + assertEquals(Response.Status.Family.REDIRECTION, response.getStatusInfo().getFamily()); + + // oauth callback returns list of groups for us + response = client.target(response.getLocation()).request().get(); + JsonArray groups = response.readEntity(JsonArray.class); + assertEquals(Json.createArrayBuilder().add("authenticated").add("code_exchange").build(), groups); + } } diff --git a/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/OpenIdCallback.java b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/OpenIdCallback.java new file mode 100644 index 00000000..9076988a --- /dev/null +++ b/openid-standalone-it/src/test/java/fish/payara/security/openid/adfs/OpenIdCallback.java @@ -0,0 +1,78 @@ +/* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2022 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + * + */ + +package fish.payara.security.openid.adfs; + +import java.security.Principal; +import java.util.logging.Logger; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonArray; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import fish.payara.security.connectors.openid.api.OpenIdContext; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +@Path("Callback") +@RequestScoped +public class OpenIdCallback { + private static final Logger LOGGER = Logger.getLogger(OpenIdCallback.class.getName()); + + @Inject + Principal principal; + + @Inject + OpenIdContext context; + + @GET + @Produces(APPLICATION_JSON) + public JsonArray userGroups() { + LOGGER.info("Request of " + principal.getName()); + LOGGER.info("Request of " + context.getSubject()); + return Json.createArrayBuilder(context.getCallerGroups()).build(); + } +}