[java-oidc-common] branch main updated: JCOMOIDC-31 - Query String serialization message encoder

Phil Smart philip.smart at jisc.ac.uk
Fri Jan 7 14:24:01 UTC 2022


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=58cd3eaadb68b12dd457353e832ff2651452ff3d

The following commit(s) were added to refs/heads/main by this push:
     new 58cd3ea  JCOMOIDC-31 - Query String serialization message encoder
58cd3ea is described below

commit 58cd3eaadb68b12dd457353e832ff2651452ff3d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 7 14:23:54 2022 +0000

    JCOMOIDC-31 - Query String serialization message encoder
    
    - Add HTTP redirect query string serlization message encoder
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-31
---
 .../oidc/profile/encoder/OIDCMessageEncoder.java   |  15 +++
 .../encoder/impl/AbstractOIDCMessageEncoder.java   | 107 +++++++++++++++++++
 .../encoder/impl/HTTPRedirectAuthnEncoder.java     | 113 +++++++++++++++++++++
 .../oidc/profile/encoder/impl/package-info.java    |  21 ++++
 .../encoder/impl/HTTPRedirectAuthnEncoderTest.java |  77 ++++++++++++++
 5 files changed, 333 insertions(+)

diff --git a/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/encoder/OIDCMessageEncoder.java b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/encoder/OIDCMessageEncoder.java
new file mode 100644
index 0000000..aa500f6
--- /dev/null
+++ b/oidc-common-profile-api/src/main/java/net/shibboleth/oidc/profile/encoder/OIDCMessageEncoder.java
@@ -0,0 +1,15 @@
+package net.shibboleth.oidc.profile.encoder;
+
+import java.util.function.Predicate;
+
+import org.opensaml.messaging.encoder.MessageEncoder;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.OIDCHttpRequestMethod;
+
+/**
+ * An OIDC message encoder interface which requires implementations to test if the request 
+ * method is supported by the encoder. 
+ */
+public interface OIDCMessageEncoder extends Predicate<OIDCHttpRequestMethod>, MessageEncoder {    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/AbstractOIDCMessageEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/AbstractOIDCMessageEncoder.java
new file mode 100644
index 0000000..fe68eb7
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/AbstractOIDCMessageEncoder.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.oidc.profile.encoder.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.encoder.servlet.AbstractHttpServletResponseMessageEncoder;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.encoder.OIDCMessageEncoder;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.net.URLBuilder;
+
+/**
+ * Base class for OIDC message encoders.
+ */
+//TODO: add a method or strategy for testing the request covers all the required parameters for a given response type 
+public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResponseMessageEncoder 
+            implements OIDCMessageEncoder {
+    
+    
+    /**
+     * Serialize OAuth 2.0 authorization parameters from the authentication request to the query string 
+     * of the URL.
+     * 
+     * @param request the authentication request.
+     * @param builder the URL builder to add the query parameters to.
+     */
+    protected void serializeAuthorizationParamsToUrl(@Nonnull final OIDCAuthenticationRequest request,
+            @Nonnull final URLBuilder builder) {
+        
+        final List<Pair<String, String>> params = createParametersFromRequest(request);
+        params.forEach(param -> builder.getQueryParams().add(param));
+    }
+    
+    /**
+     * Serialize OAuth 2.0 authorization parameters from the authentication request to a query string.
+     * 
+     * @param request the authentication request query string.
+     * 
+     * @return the query string.
+     */
+    protected String serializeAuthorizationParamsToQueryString(@Nonnull final OIDCAuthenticationRequest request) {
+        //TODO maybe a better way to do this than the full URL builder?
+        final URLBuilder builder = new URLBuilder();
+        final List<Pair<String, String>> params = createParametersFromRequest(request);
+        params.forEach(param -> builder.getQueryParams().add(param));
+        return builder.buildQueryString();
+    }
+    
+    /**
+     * Create a list of OAuth 2.0 authorization parameters from the {@link OIDCAuthenticationRequest} object.
+     * 
+     * @param req the authentication request
+     * 
+     * @return a list of authorization parameters.
+     */
+    protected List<Pair<String, String>> createParametersFromRequest(@Nonnull final OIDCAuthenticationRequest req) {
+        
+
+        final List<Pair<String, String>> params = new ArrayList<>();
+        
+        params.add(new Pair<>("client_id", req.getClientID().getValue()));
+        params.add(new Pair<>("scope", req.getScope().toString()));
+        
+        if (req.getResponseType() != null) {
+            params.add(new Pair<>("response_type", req.getResponseType().toString()));
+        }
+        if (req.getResponseMode() != null) {
+            params.add(new Pair<>("response_mode", req.getResponseMode().getValue()));
+        }
+        if (req.getRedirectURI() != null) {
+            params.add(new Pair<>("redirect_uri", req.getRedirectURI().toString()));
+        }   
+        if (req.getState() != null) {
+            params.add(new Pair<>("state", req.getState().getValue()));
+        }
+        if (req.getPrompt() != null) {
+            params.add(new Pair<>("prompt", req.getPrompt().toString()));
+        }
+        if (req.getRequestObject() != null) {          
+            params.add(new Pair<>("request", req.getRequestObject().serialize()));          
+        }        
+        //TODO: requestURI, includedGrantedScopes?, resource_uris?
+        return params;
+
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoder.java
new file mode 100644
index 0000000..34a53da
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoder.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.oidc.profile.encoder.impl;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+
+import javax.annotation.Nonnull;
+import javax.servlet.http.HttpServletResponse;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.encoder.MessageEncoder;
+import org.opensaml.messaging.encoder.MessageEncodingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.OIDCHttpRequestMethod;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.net.HttpServletSupport;
+import net.shibboleth.utilities.java.support.net.URLBuilder;
+
+/**
+ * A {@link MessageEncoder message encoder} that encodes an OpenID authentication request by 
+ * Query String Serialization and sends a HTTP redirect response.
+ */
+//TODO maybe this could encode authz responses as well? to replace the NimbusResponseEncoder
+public class HTTPRedirectAuthnEncoder extends AbstractOIDCMessageEncoder  {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(HTTPRedirectAuthnEncoder.class);
+    
+    @Override
+    public boolean test(@Nonnull final OIDCHttpRequestMethod requestMethod) {
+       return requestMethod == OIDCHttpRequestMethod.GET;
+    }    
+
+    @Override
+    protected void doEncode() throws MessageEncodingException {
+        
+        log.debug("Encoding OIDC authentication request using Query String Serialization");
+        final MessageContext messageContext = getMessageContext();
+        final Object outboundMessage = messageContext.getMessage();
+        if (!(outboundMessage instanceof OIDCAuthenticationRequest)) {
+            throw new MessageEncodingException("No outbound OIDC authentication request message "
+                    + "contained in message context");
+        }
+
+        final String redirectURL = buildRedirectURL(messageContext, (OIDCAuthenticationRequest)outboundMessage);
+        
+        final HttpServletResponse response = getHttpServletResponse();
+        HttpServletSupport.addNoCacheHeaders(response);
+        HttpServletSupport.setUTF8Encoding(response);
+        HttpServletSupport.setContentType(response, "application/x-www-form-urlencoded");
+
+        try {
+            log.trace("Redirecting user-agent to '{}'",redirectURL);
+            response.sendRedirect(redirectURL);
+        } catch (final IOException e) {
+            throw new MessageEncodingException("Problem sending HTTP redirect", e);
+        }
+        
+    }
+    
+    /**
+     * Build the URL to redirect the client to using parameters in the authentication request.
+     * 
+     * @param messageContext the current message context
+     * @param request the authentication request
+     * 
+     * @return a URL to redirect the client to.
+     * 
+     * @throws MessageEncodingException if there is an issue building the URL or the endpoint is null.
+     */
+    protected String buildRedirectURL(final MessageContext messageContext, final OIDCAuthenticationRequest request)
+            throws MessageEncodingException {
+        
+        if (request.getEndpointURI() == null) {
+            throw new MessageEncodingException("No endpoint URI specified, URL can not be built");
+        }
+        URLBuilder urlBuilder = null;
+        try {
+            if (request.getEndpointURI() == null) {
+                throw new MessageEncodingException("Endpoint URL is null");
+            }
+            //TODO check the endpoint is always the baseURL, otherwise this may go wrong.
+            urlBuilder = new URLBuilder(request.getEndpointURI().toString());
+        } catch (final MalformedURLException e) {
+            throw new MessageEncodingException("Endpoint URL " + request.getEndpointURI() + " is not a valid URL", e);
+        }
+        
+        serializeAuthorizationParamsToUrl(request, urlBuilder);       
+        return urlBuilder.buildURL();
+        
+    }
+    
+    
+
+}
diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/package-info.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/package-info.java
new file mode 100644
index 0000000..dfe4580
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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.
+ */
+
+/**
+ * OIDC message encoders.
+ */
+package net.shibboleth.oidc.profile.encoder.impl;
\ No newline at end of file
diff --git a/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoderTest.java b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoderTest.java
new file mode 100644
index 0000000..b9a4244
--- /dev/null
+++ b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPRedirectAuthnEncoderTest.java
@@ -0,0 +1,77 @@
+package net.shibboleth.oidc.profile.encoder.impl;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.net.URI;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.encoder.MessageEncodingException;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.UninitializedComponentException;
+
+/** Test for the HTTPRedirectAuthnEncoder.*/
+public class HTTPRedirectAuthnEncoderTest {
+    
+    /** The encoder to test.*/
+    private HTTPRedirectAuthnEncoder encoder;
+    
+    /** Mock servlet response.*/
+    private MockHttpServletResponse mockResponse;
+    
+    /** The request.*/
+    private OIDCAuthenticationRequest request;
+    
+    /** The Message context.*/
+    private MessageContext context;
+    
+    
+    @BeforeMethod public void setUp() throws Exception {    
+        encoder = new HTTPRedirectAuthnEncoder();      
+        context = new MessageContext();
+        request = new OIDCAuthenticationRequest(new ClientID("clientID"));
+        // This needs to be dynamic
+        request.setResponseType(ResponseType.CODE);
+        request.setEndpointURI(new URI("https://somewhere.com/oauth2/authz"));
+        request.setRedirectURI(new URI("https://localhost:8080/callback"));
+        context.setMessage(request);
+        encoder.setMessageContext(context);
+        mockResponse = new MockHttpServletResponse();
+        encoder.setHttpServletResponse(mockResponse);
+    }
+    
+    @Test
+    public void testSuccesfullEncoding() throws Exception {
+        encoder.initialize();
+        encoder.encode();
+        final String response = mockResponse.getRedirectedUrl();
+        assertNotNull(response);
+        // These are all required
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("response_type"));
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("scope"));
+       
+    }
+    
+    @Test(expectedExceptions = UninitializedComponentException.class)
+    public void testUninitialized() throws MessageEncodingException {
+        encoder.encode();
+    }
+    
+    @Test(expectedExceptions = MessageEncodingException.class)
+    public void testNullEndpointURL() throws Exception {
+        request = new OIDCAuthenticationRequest(new ClientID("clientID"));
+        context.setMessage(request);
+        encoder.initialize();
+        encoder.encode();
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list