[java-oidc-common] branch main updated: JCOMOIDC-32 - Form serialization message encoder

Phil Smart philip.smart at jisc.ac.uk
Fri Jan 7 14:27:43 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=5fe24b24e0de1b4926bc58ddd2dab8b4ff00af6d

The following commit(s) were added to refs/heads/main by this push:
     new 5fe24b2  JCOMOIDC-32 - Form serialization message encoder
5fe24b2 is described below

commit 5fe24b24e0de1b4926bc58ddd2dab8b4ff00af6d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 7 14:27:37 2022 +0000

    JCOMOIDC-32 - Form serialization message encoder
    
    - Add HTTP POST message encoder which uses Form serialization.
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-32
---
 .../profile/encoder/impl/HTTPPostAuthnEncoder.java | 145 +++++++++++++++++++++
 .../encoder/impl/HTTPPostAuthnEncoderTest.java     |  80 ++++++++++++
 .../resources/templates/oidc-request-form-post.vm  |  46 +++++++
 3 files changed, 271 insertions(+)

diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoder.java
new file mode 100644
index 0000000..e7cddfe
--- /dev/null
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoder.java
@@ -0,0 +1,145 @@
+/*
+ * 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.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+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.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.codec.HTMLEncoder;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.net.HttpServletSupport;
+
+/**
+ * A {@link MessageEncoder message encoder} that encodes an OpenID authentication request by
+ * HTTP Form POST Serialization.
+ */
+public class HTTPPostAuthnEncoder extends AbstractOIDCMessageEncoder {
+    
+    /** Default template ID for using FORM POST request type. */
+    @Nonnull @NotEmpty public static final String DEFAULT_TEMPLATE_ID = "/templates/oidc-request-form-post.vm";
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(HTTPPostAuthnEncoder.class);
+    
+    /** Velocity engine used to evaluate the template when using FORM POST response mode. */
+    @Nullable private VelocityEngine velocityEngine;
+
+    /** ID of the Velocity template used when using FORM POST response mode. */
+    @Nonnull @NotEmpty private String velocityTemplateId = DEFAULT_TEMPLATE_ID;
+    
+    /**
+     * Set the Velocity template id.
+     * 
+     * <p>
+     * Defaults to {@link #DEFAULT_TEMPLATE_ID}.
+     * </p>
+     * 
+     * @param newVelocityTemplateId the new Velocity template id
+     */
+    public void setVelocityTemplateId(@Nonnull @NotEmpty final String newVelocityTemplateId) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        velocityTemplateId = 
+                Constraint.isNotEmpty(newVelocityTemplateId, "Velocity template id must not not be null or empty");
+    }
+
+    /**
+     * Set the VelocityEngine instance.
+     * 
+     * @param newVelocityEngine the new VelocityEngine instane
+     */
+    public void setVelocityEngine(@Nonnull final VelocityEngine newVelocityEngine) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        velocityEngine = Constraint.isNotNull(newVelocityEngine, "Velocity engine can not be null");
+    }
+    
+
+    @Override
+    public boolean test(@Nonnull final OIDCHttpRequestMethod requestMethod) {
+       return requestMethod == OIDCHttpRequestMethod.POST;
+    }   
+    
+    /**
+     * Construct form POST.
+     * 
+     * @param request the authentication request.
+     * @return response message as velocity context.
+     */
+    private VelocityContext doPostEncode(@Nonnull final OIDCAuthenticationRequest request) {
+        final VelocityContext context = new VelocityContext();
+        final List<Pair<String, String>> params = createParametersFromRequest(request);
+        params.forEach(param -> context.put(param.getFirst(), param.getSecond()));
+        context.put("action", HTMLEncoder.encodeForHTMLAttribute(request.getEndpointURI().toString()));
+        log.trace("Velocity context {}", params);
+        return context;
+    }    
+
+    @Override
+    protected void doEncode() throws MessageEncodingException {
+        
+        if (velocityEngine == null) {
+            throw new MessageEncodingException("VelocityEngine must be supplied for form post request mode");
+        }
+        
+        log.debug("Encoding OIDC authentication request using HTTP Form Post 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");
+        }
+        try {
+            final HttpServletResponse response = getHttpServletResponse();
+            HttpServletSupport.addNoCacheHeaders(response);
+            HttpServletSupport.setUTF8Encoding(response);
+            HttpServletSupport.setContentType(response, "text/html");
+            final VelocityContext context = doPostEncode((OIDCAuthenticationRequest) outboundMessage);
+            try (final Writer out = new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8)) {
+                velocityEngine.mergeTemplate(velocityTemplateId, "UTF-8", context, out);
+                out.flush();
+            }
+        } catch (final Exception e) {
+            log.error("Error creating authorization POST request: {}", e.getMessage());
+            throw new MessageEncodingException("Error creating authorization POST request", e);
+        }
+        
+        
+    }
+
+
+}
diff --git a/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoderTest.java b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoderTest.java
new file mode 100644
index 0000000..837c609
--- /dev/null
+++ b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoder/impl/HTTPPostAuthnEncoderTest.java
@@ -0,0 +1,80 @@
+package net.shibboleth.oidc.profile.encoder.impl;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+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.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.UninitializedComponentException;
+import net.shibboleth.utilities.java.support.velocity.VelocityEngine;
+
+/** Test for the HTTPPostAuthnEncoder.*/
+public class HTTPPostAuthnEncoderTest {
+    
+    /** Mock servlet response.*/
+    private MockHttpServletResponse mockResponse;
+    
+    /** The encoder to test.*/
+    private HTTPPostAuthnEncoder encoder;
+    
+    /** The authentication request.*/
+    private OIDCAuthenticationRequest request;
+    
+    /** The message context.*/
+    private MessageContext context;
+    
+    
+    @BeforeMethod public void setUp() throws Exception {    
+        encoder = new HTTPPostAuthnEncoder();      
+        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);
+        encoder.setVelocityEngine(VelocityEngine.newVelocityEngine());
+        mockResponse = new MockHttpServletResponse();
+        encoder.setHttpServletResponse(mockResponse);
+    }
+    
+    @Test
+    public void testSuccesfullEncoding() throws MessageEncodingException, ComponentInitializationException, UnsupportedEncodingException {
+        encoder.initialize();
+        encoder.encode();
+        final String response = mockResponse.getContentAsString();
+        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();
+    }
+
+}
diff --git a/oidc-common-profile-impl/src/test/resources/templates/oidc-request-form-post.vm b/oidc-common-profile-impl/src/test/resources/templates/oidc-request-form-post.vm
new file mode 100644
index 0000000..1a294c3
--- /dev/null
+++ b/oidc-common-profile-impl/src/test/resources/templates/oidc-request-form-post.vm
@@ -0,0 +1,46 @@
+##
+## Velocity Template for OIDC Form Post response mode.
+##
+##
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8" />
+</head>
+
+<body onload="document.forms[0].submit()">
+    <noscript>
+        <p>
+            <strong>Note:</strong> Since your browser does not support JavaScript, you must press the Continue button once to proceed.
+        </p>
+    </noscript>
+
+    <form action="${action}" method="post">
+        <div>
+            #if($client_id)
+            <input type="hidden" name="client_id" value="${client_id}" />#end #if($scope)
+
+            <input type="hidden" name="scope" value="${scope}" />#end #if($response_type)
+
+            <input type="hidden" name="response_type" value="${response_type}" />#end #if($response_mode)
+
+            <input type="hidden" name="response_mode" value="${response_mode}" />#end #if($redirect_uri)
+
+            <input type="hidden" name="redirect_uri" value="${redirect_uri}" />#end #if($state)
+
+            <input type="hidden" name="state" value="${state}" />#end #if($prompt)
+
+            <input type="hidden" name="prompt" value="${prompt}" />#end #if($request)
+
+            <input type="hidden" name="request" value="${request}" />#end 
+        </div>
+        <noscript>
+            <div>
+                <input type="submit" value="Continue" />
+            </div>
+        </noscript>
+    </form>
+</body>
+
+</html>
\ No newline at end of file

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


More information about the commits mailing list