[java-idp-plugin-oidc-rp] branch main updated: Add support for new authz/authn message encoders and handlers
Phil Smart
philip.smart at jisc.ac.uk
Fri Jan 7 10:38:10 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=54c09c8b52790bdbc402f9c5acb28af67efcebf9
The following commit(s) were added to refs/heads/main by this push:
new 54c09c8 Add support for new authz/authn message encoders and handlers
54c09c8 is described below
commit 54c09c8b52790bdbc402f9c5acb28af67efcebf9
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 7 10:38:03 2022 +0000
Add support for new authz/authn message encoders and handlers
Some other cleanup
Fix SWF flow test
Make changes to support new OIDC.SSO profile classes
---
.../authn/oidc/rp/context/OIDCAuthnContext.java | 62 ++++
.../context/OIDCAuthorizationRequestContext.java | 11 -
idp-oidc-rp-impl/pom.xml | 22 +-
.../plugin/authn/oidc/rp/impl/AddAuthzRequest.java | 95 ++++++-
.../oidc/rp/impl/AuthorizationController.java | 186 +++++++-----
...OutboundAuthorizationRequestMessageContext.java | 22 +-
.../rp/impl/InitializeRelyingPartyContext.java | 28 +-
.../authn/oidc/rp/impl/OIDCProxySupport.java | 61 ++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 19 +-
.../oidc-relying-party-authn-beans.xml | 46 ++-
.../oidc-relying-party-authn-flow.xml | 45 +--
.../authn/providermetadata-resolver-system.xml | 2 +-
.../resources/templates/oidc-request-form-post.vm | 46 +++
.../oidc/rp/impl/AuthorizationControllerTest.java | 311 +++++++++++++++++++++
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 10 +-
.../resources/conf/additional-system-beans.xml | 28 ++
16 files changed, 829 insertions(+), 165 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
new file mode 100644
index 0000000..76cb049
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
@@ -0,0 +1,62 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.handler.MessageHandler;
+import org.opensaml.profile.action.ProfileAction;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Manages state during proxied OIDC authentication.
+ */
+public class OIDCAuthnContext extends BaseContext {
+
+ /** Outbound message handler to run prior to encoding. */
+ @Nullable private MessageHandler outboundMessageHandler;
+
+ /** Profile action to execute to produce outbound message response. */
+ @Nonnull private final ProfileAction encodeMessageAction;
+
+ /**
+ * Constructor.
+ *
+ * @param action message-encoding profile action
+ */
+ public OIDCAuthnContext(@Nonnull final ProfileAction action) {
+ encodeMessageAction = Constraint.isNotNull(action, "Profile action cannot be null");
+ }
+
+ /**
+ * Get the message-encoding profile action.
+ *
+ * @return profile action
+ */
+ @Nonnull public ProfileAction getEncodeMessageAction() {
+ return encodeMessageAction;
+ }
+
+ /**
+ * Get the outbound {@link MessageHandler} to run prior to encoding.
+ *
+ * @return the outbound {@link MessageHandler}
+ */
+ @Nullable public MessageHandler getOutboundMessageHandler() {
+ return outboundMessageHandler;
+ }
+
+ /**
+ * Set the outbound {@link MessageHandler} to run prior to encoding.
+ *
+ * @param handler outbound {@link MessageHandler} to set
+ *
+ * @return this context
+ */
+ @Nonnull public OIDCAuthnContext setOutboundMessageHandler(@Nullable final MessageHandler handler) {
+ outboundMessageHandler = handler;
+ return this;
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCAuthorizationRequestContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCAuthorizationRequestContext.java
deleted file mode 100644
index a768639..0000000
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCAuthorizationRequestContext.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context;
-
-import org.opensaml.messaging.context.BaseContext;
-
-/**
- * Subcontext carrying information to form an authorization request for an OpenID Connect Provider. This context
- * appears as a subcontext of the {@link org.opensaml.messaging.context.MessageContext}.
- */
-public class OIDCAuthorizationRequestContext extends BaseContext {
-
-}
diff --git a/idp-oidc-rp-impl/pom.xml b/idp-oidc-rp-impl/pom.xml
index 019f384..e4ecd8e 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -81,21 +81,16 @@
<artifactId>oidc-common-profile-api</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>net.shibboleth.oidc</groupId>
+ <artifactId>oidc-common-profile-impl</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
- <!-- Tmp OP deps until things move to commons -->
- <!-- <dependency>
- <groupId>net.shibboleth.idp.plugin.oidc</groupId>
- <artifactId>idp-plugin-oidc-op-api</artifactId>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp.plugin.oidc</groupId>
- <artifactId>idp-plugin-oidc-op-impl</artifactId>
- </dependency> -->
- <!-- Service API and Plugin Description dependency -->
<dependency>
<groupId>${idp.groupId}</groupId>
<artifactId>idp-admin-api</artifactId>
@@ -119,7 +114,7 @@
<scope>test</scope>
</dependency>
<dependency>
- <groupId>net.shibboleth.idp</groupId>
+ <groupId>${idp.groupId}</groupId>
<artifactId>idp-conf</artifactId>
<scope>test</scope>
</dependency>
@@ -128,6 +123,11 @@
<artifactId>idp-conf-impl</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${opensaml.groupId}</groupId>
+ <artifactId>opensaml-messaging-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
index d82cb36..cfa5255 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
@@ -1,28 +1,71 @@
+/*
+ * 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.idp.plugin.authn.oidc.rp.impl;
+import java.net.URI;
+import java.net.URISyntaxException;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.ParentContextLookup;
import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+
+/**
+ * Action that creates an {@link OIDCAuthenticationRequest} and sets it as the message returned by
+ * {@link ProfileRequestContext#getOutboundMessageContext()}.
+ *
+ * <p>Note, this is an OIDC authentication request on top of an OAuth 2.0 authorization request.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ *
+ * @post ProfileRequestContext.getOutboundMessageContext().getMessage() != null
+ */
public class AddAuthzRequest extends AbstractAuthenticationAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(AddAuthzRequest.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(AddAuthzRequest.class);
+
+ /** Overwrite an existing message? */
+ private boolean overwriteExisting;
/** Applicable profile configuration. */
- //TODO not currently used - needs profile implementation actions to work
- @Nullable private BrowserSSOProfileConfiguration profileConfiguration;
+ @Nullable private OIDCAuthorizationConfiguration profileConfiguration;
/** Constructor.*/
public AddAuthzRequest() {
@@ -30,6 +73,17 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
}
+ /**
+ * Set whether to overwrite an existing message.
+ *
+ * @param flag flag to set
+ */
+ public void setOverwriteExisting(final boolean flag) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ overwriteExisting = flag;
+ }
+
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@Nonnull final AuthenticationContext authenticationContext) {
@@ -40,15 +94,28 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
if (rpCtx != null && rpCtx.getConfiguration() != null &&
- rpCtx.getProfileConfig() instanceof BrowserSSOProfileConfiguration) {
- profileConfiguration = (BrowserSSOProfileConfiguration) rpCtx.getProfileConfig();
+ rpCtx.getProfileConfig() instanceof OIDCAuthorizationConfiguration) {
+ profileConfiguration = (OIDCAuthorizationConfiguration) rpCtx.getProfileConfig();
}
if (profileConfiguration == null) {
- log.error("{} BrowserSSOProfileConfiguration not found", getLogPrefix());
+ log.error("{} OIDCCoreProtocolConfiguration not found", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
return false;
}
+ final MessageContext outboundMessageCtx = profileRequestContext.getOutboundMessageContext();
+ if (outboundMessageCtx == null) {
+ log.debug("{} No outbound message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ } else if (!overwriteExisting && outboundMessageCtx.getMessage() != null) {
+ log.debug("{} Outbound message context already contains a message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ outboundMessageCtx.setMessage(null);
+
return true;
}
@@ -59,6 +126,20 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
log.debug("{} Building AuthzRequest for upstream OP ({})",
getLogPrefix(), authenticationContext.getAuthenticatingAuthority());
+ try {
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("clientID"));
+ // FIXME: This needs to be dynamic, is a demo for now
+ request.setResponseType(ResponseType.CODE);
+ request.setEndpointURI(new URI("https://somewhere.com/oauth2/authz"));
+ request.setRedirectURI(new URI("https://localhost:8080/callback"));
+
+ log.debug("{} Built authorization request for endpoint '{}'",getLogPrefix(), request.getEndpointURI());
+ profileRequestContext.getOutboundMessageContext().setMessage(request);
+ } catch (URISyntaxException e) {
+ log.error("{} Unable to create authorization request for downstream OP '{}'", getLogPrefix(), "OP");
+
+ }
+
}
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
index 49fcd41..d9a17f6 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
@@ -18,7 +18,7 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.io.IOException;
-import java.net.URISyntaxException;
+import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
@@ -27,6 +27,10 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.EventContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,11 +38,14 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
+import com.nimbusds.oauth2.sdk.id.State;
+
import net.shibboleth.idp.authn.ExternalAuthentication;
import net.shibboleth.idp.authn.ExternalAuthenticationException;
import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
/**
*
@@ -59,76 +66,101 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
* <p>
* The {@link HttpServletRequest} is placed inside the {@link OpenIDConnectContext} for interrogation later in the flow.
* </p>
- *
- * @since 4.0.0
+ * FIXME: this description
*/
@ThreadSafe
@Controller
- at RequestMapping("/Authn/OIDC/RP")
+ at RequestMapping("%{shibboleth.authn.OIDC.externalAuthnPath:/Authn/OIDC/RP}")
public class AuthorizationController {
-
- /** Prefix for the session attribute ids. */
- @Nonnull public static final String SESSION_ATTR_PREFIX =
- "net.shibboleth.idp.authn.oidc.impl.OpenIdConnectStartServlet.";
-
- /** Session attribute id for flow conversation key. */
- @Nonnull public static final String SESSION_ATTR_FLOWKEY = SESSION_ATTR_PREFIX + "key";
-
- /** Session attribute id for {@link OpenIDConnectContext}. */
- @Nonnull public static final String SESSION_ATTR_SUCTX = SESSION_ATTR_PREFIX + "openIdConnectContext";
-
- /** Serial UID. */
- private static final long serialVersionUID = -3162157736238514852L;
-
+
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
+
+ /** Lookup strategy to locate the nested ProfileRequestContext. */
+ @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextLookupStrategy;
+
+ /** Lookup strategy to locate the SAML context. */
+ @Nonnull private Function<ProfileRequestContext,OIDCAuthnContext> oidcContextLookupStrategy;
+
+ /** Constructor.*/
+ public AuthorizationController() {
+ // PRC -> AC -> nested PRC
+ profileRequestContextLookupStrategy = new ChildContextLookup<>(ProfileRequestContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+
+ // PRC -> AC -> OIDCAuthnContext
+ oidcContextLookupStrategy = new ChildContextLookup<>(OIDCAuthnContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+ }
/**
- * Begin an authorization request to the configured OP.
- *
- * TODO FINISH
+ * Begin an authorization request to the configured downstream OP.
*
* @param httpRequest the servlet request.
* @param httpResponse the servlet response.
*
* @throws ServletException throw if there is an error constructing an authz request.
* @throws IOException throw if there is an error constructing an authz request.
+ * @throws ExternalAuthenticationException
*/
- @GetMapping("/auth")
+ @GetMapping("/authz")
public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest,
- @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
-
- try {
- final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
- httpRequest.getSession().setAttribute(SESSION_ATTR_FLOWKEY, key);
-
- @SuppressWarnings("rawtypes") final ProfileRequestContext profileRequestContext =
- (ProfileRequestContext) httpRequest.getAttribute(ProfileRequestContext.BINDING_KEY);
- if (profileRequestContext == null) {
- throw new ExternalAuthenticationException("Could not access profileRequestContext from the request");
- }
- final AuthenticationContext authenticationContext =
- profileRequestContext.getSubcontext(AuthenticationContext.class);
- if (authenticationContext == null) {
- throw new ExternalAuthenticationException("Could not get AuthenticationContext from the request");
- }
- final OpenIDConnectContext openIDConnectContext =
- authenticationContext
- .getSubcontext(OpenIDConnectContext.class);
- if (openIDConnectContext == null) {
- throw new ExternalAuthenticationException(
- "Could not get OpenIdConnectContext from the request");
+ @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException, ExternalAuthenticationException {
+
+ final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
+ final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+
+ final OIDCAuthnContext oidcContext = oidcContextLookupStrategy.apply(prc);
+ if (oidcContext == null) {
+ log.error("OIDCAuthnContext not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+
+ final ProfileRequestContext nestedPRC = profileRequestContextLookupStrategy.apply(prc);
+ if (nestedPRC == null) {
+ log.error("Nested ProfileRequestContext not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+
+ if (nestedPRC.getOutboundMessageContext() != null &&
+ nestedPRC.getOutboundMessageContext().getMessage() instanceof OIDCAuthenticationRequest) {
+ // Add key and nonce to state
+ final String state = OIDCProxySupport.generateState(OIDCProxySupport.generateNonce(32), key);
+ ((OIDCAuthenticationRequest)nestedPRC.getOutboundMessageContext().getMessage()).setState(new State(state));
+
+ } else {
+ log.error("Outbound Authorization message not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_MESSAGE);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+
+ try {
+ if (oidcContext.getOutboundMessageHandler() != null) {
+ oidcContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext()); }
+
+ oidcContext.getEncodeMessageAction().execute(nestedPRC);
+ // Handle error added by the EncodeMessage action.
+ final EventContext eventCtx = prc.getSubcontext(EventContext.class);
+ if (eventCtx != null && eventCtx.getEvent() != null
+ && !EventIds.PROCEED_EVENT_ID.equals(eventCtx.getEvent())) {
+ log.error("Message encoding action signaled non-proceed event {}", eventCtx.getEvent());
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY,
+ eventCtx.getEvent().toString());
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
}
- //TODO do not put state in session, put in the request.
- httpRequest.getSession().setAttribute(SESSION_ATTR_SUCTX, openIDConnectContext);
- log.debug("Redirecting http-agent to {}", openIDConnectContext.getAuthenticationRequestURI());
- httpResponse.sendRedirect(openIDConnectContext.getAuthenticationRequestURI().toString());
- } catch (final ExternalAuthenticationException e) {
- log.error("Error processing external authentication request", e);
- throw new ServletException("Error processing external authentication request", e);
+ } catch (final MessageHandlerException e) {
+ log.error("Caught message handling exception", e);
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.MESSAGE_PROC_ERROR);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
}
-
+
}
/**
@@ -145,30 +177,30 @@ public class AuthorizationController {
@Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
try {
- final HttpSession session = httpRequest.getSession();
- if (session == null) {
- throw new ExternalAuthenticationException("No session exists, this URL shouldn't be called directly");
- }
- final String key = StringSupport.trimOrNull((String) httpRequest.getSession()
- .getAttribute(AuthorizationController.SESSION_ATTR_FLOWKEY));
- if (key == null) {
- throw new ExternalAuthenticationException(
- "Could not find value for " + AuthorizationController.SESSION_ATTR_FLOWKEY);
- }
- final OpenIDConnectContext openIDConnectContext =
- (OpenIDConnectContext) httpRequest.getSession()
- .getAttribute(AuthorizationController.SESSION_ATTR_SUCTX);
- if (openIDConnectContext == null) {
- throw new ExternalAuthenticationException(
- "Could not find value for " + AuthorizationController.SESSION_ATTR_SUCTX);
- }
- log.trace("Attempting URL {}?{}", httpRequest.getRequestURL(), httpRequest.getQueryString());
- try {
- openIDConnectContext.setAuthenticationResponseURI(httpRequest);
- } catch (final URISyntaxException e) {
- throw new ExternalAuthenticationException("Could not parse response URI", e);
- }
- ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+// final HttpSession session = httpRequest.getSession();
+// if (session == null) {
+// throw new ExternalAuthenticationException("No session exists, this URL shouldn't be called directly");
+// }
+// final String key = StringSupport.trimOrNull((String) httpRequest.getSession()
+// .getAttribute(AuthorizationController.SESSION_ATTR_FLOWKEY));
+// if (key == null) {
+// throw new ExternalAuthenticationException(
+// "Could not find value for " + AuthorizationController.SESSION_ATTR_FLOWKEY);
+// }
+// final OpenIDConnectContext openIDConnectContext =
+// (OpenIDConnectContext) httpRequest.getSession()
+// .getAttribute(AuthorizationController.SESSION_ATTR_SUCTX);
+// if (openIDConnectContext == null) {
+// throw new ExternalAuthenticationException(
+// "Could not find value for " + AuthorizationController.SESSION_ATTR_SUCTX);
+// }
+// log.trace("Attempting URL {}?{}", httpRequest.getRequestURL(), httpRequest.getQueryString());
+// try {
+// openIDConnectContext.setAuthenticationResponseURI(httpRequest);
+// } catch (final URISyntaxException e) {
+// throw new ExternalAuthenticationException("Could not parse response URI", e);
+// }
+ ExternalAuthentication.finishExternalAuthentication("key-wrong", httpRequest, httpResponse);
} catch (final ExternalAuthenticationException e) {
log.error("Could not finish the external authentication", e);
throw new ServletException("Error finishing the external authentication", e);
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
index ee7ac0a..2296d48 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
@@ -1,3 +1,20 @@
+/*
+ * 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.idp.plugin.authn.oidc.rp.impl;
import java.util.function.Function;
@@ -13,14 +30,13 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCAuthorizationRequestContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
-
+// FIXME: think of the logic
public class InitializeOutboundAuthorizationRequestMessageContext extends AbstractProfileAction {
/** Class logger. */
@@ -99,7 +115,7 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
final MessageContext msgCtx = new MessageContext();
profileRequestContext.setOutboundMessageContext(msgCtx);
- msgCtx.addSubcontext(new OIDCAuthorizationRequestContext());
+ //msgCtx.addSubcontext(new OIDCAuthorizationRequestContext());
log.debug("{} Initialized outbound message context", getLogPrefix());
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
index a2544b6..3d1376e 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
@@ -1,3 +1,20 @@
+/*
+ * 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.idp.plugin.authn.oidc.rp.impl;
import java.util.function.Function;
@@ -22,6 +39,16 @@ import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
+/**
+ * Action that adds a {@link RelyingPartyContext} to the current {@link ProfileRequestContext} tree via a creation
+ * function.
+ *
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
+ * @post ProfileRequestContext.getSubcontext(RelyingPartyContext.class) != null with relying party id set.
+ */
+// TODO: this is pretty much identical to that in the OP, should that be moved into commons?
public class InitializeRelyingPartyContext extends AbstractProfileAction {
/** Class logger. */
@@ -111,7 +138,6 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
log.debug("Attaching RelyingPartyContext for OP {}", issuerId);
rpContext.setRelyingPartyId(issuerId);
final OIDCProviderMetadataContext oidcContext = oidcProviderMetadataContextLookupStrategy.apply(profileRequestContext);
- //TODO is this sufficient to set verified to true? Yes as a verified reyling party if metadata attached
if (oidcContext != null && oidcContext.getProviderInformation() != null
&& issuerId.equals(oidcContext.getProviderInformation().getIssuer().getValue())) {
log.debug("{} Setting the OP context to 'verified'", getLogPrefix());
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
new file mode 100644
index 0000000..c214c76
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
@@ -0,0 +1,61 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.security.SecureRandom;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.codec.binary.Hex;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Support class for OIDC proxy related implementations.
+ */
+//TODO Duo has similar support class methods, maybe merge.
+public final class OIDCProxySupport {
+
+ /** Private constructor.*/
+ private OIDCProxySupport() {
+
+ }
+
+ /**
+ * Generates a random identifier to be used as a nonce.
+ *
+ * @param length the length of the parameter.
+ *
+ * @return the randomly generated nonce value.
+ */
+ @Nonnull static String generateNonce(@Nonnull final Integer length) {
+ final SecureRandom secureRandom = new SecureRandom();
+ final StringBuilder sb = new StringBuilder();
+ while(sb.length() < length){
+ sb.append(Integer.toHexString(secureRandom.nextInt()));
+ }
+ return sb.toString().substring(0, length);
+ }
+
+
+ /**
+ * <p>Generate a state parameter from a nonce component and an execution key component.</p>
+ *
+ * <p>The nonce is separated from the key by a dot e.g. {@literal <nonce>.<keyHex>}.</p>
+ *
+ * <p>The nonce is assumed to be already encoded in its transmission format e.g. Hex. The key is
+ * hex encoded before it is combined with the nonce. The result is assumed URL encoded e.g. inside
+ * the allowed set of URI characters or, no character in the state is from the URI reserved set.</p>
+ *
+ * @param nonce the nonce component.
+ * @param key the key component. The key is hex encoded before it is added to the generated state.
+ *
+ * @return the combined state component.
+ */
+ @Nonnull static String generateState(@Nonnull final String nonce, @Nonnull final String key) {
+ Constraint.isNotNull(nonce, "NonceHex key can not be null");
+ Constraint.isNotNull(key, "Webflow execution key can not be null");
+
+ final String keyHex = Hex.encodeHexString(key.getBytes());
+ return nonce+"."+keyHex;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index a7d633b..ef18587 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -45,20 +45,23 @@
<bean id="AbstractOIDCProfile" abstract="true"
p:securityConfiguration-ref="%{idp.security.authn.oidc.rp.config:shibboleth.oidc.DefaultSecurityConfiguration}" />
-
- <bean id="OIDC.SSO" parent="AbstractOIDCProfile" lazy-init="true"
- class="net.shibboleth.oidc.profile.config.OIDCCoreProtocolConfiguration"
+
+ <bean id="AbstractOIDCSSOProfile" parent="AbstractOIDCProfile" abstract="true"
p:issuer-ref="issuer"
+ p:tokenEndpointAuthMethods="%{idp.oidc.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
+ p:forcePKCE="%{idp.authn.oidc.rp.forcePKCE:false}"
+ p:allowPKCEPlain="%{idp.authn.oidc.rp.allowPKCEPlain:false}"
p:iDTokenLifetime="%{idp.authn.oidc.rp.idToken.defaultLifetime:PT1H}"
p:accessTokenLifetime="%{idp.authn.oidc.rp.accessToken.defaultLifetime:PT10M}"
- p:authorizeCodeLifetime="%{idp.authn.oidc.rp.authorizeCode.defaultLifetime:PT5M}"
p:refreshTokenLifetime="%{idp.authn.oidc.rp.refreshToken.defaultLifetime:PT2H}"
- p:tokenEndpointAuthMethods="%{idp.authn.oidc.rp.tokenEndpointAuthMethods:client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt}"
- p:forcePKCE="%{idp.authn.oidc.rp.forcePKCE:false}"
- p:allowPKCEPlain="%{idp.authn.oidc.rp.allowPKCEPlain:false}"
+ p:alwaysIncludedAttributes="%{idp.authn.oidc.rp.alwaysIncludedAttributes:}" />
+
+ <bean id="OIDC.SSO" parent="AbstractOIDCSSOProfile" lazy-init="true"
+ class="net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration"
+ p:httpRequestMethod="%{idp.authn.oidc.rp.httpRequestMethod:GET}"
+ p:authorizeCodeLifetime="%{idp.authn.oidc.rp.authorizeCode.defaultLifetime:PT5M}"
p:encodeConsentInTokens="%{idp.authn.oidc.rp.encodeConsentInTokens:false}"
p:encodedAttributes="%{idp.authn.oidc.rp.encodedAttributes:%{idp.oidc.embeddedAttributes:}}"
- p:alwaysIncludedAttributes="%{idp.authn.oidc.rp.alwaysIncludedAttributes:}"
p:deniedUserInfoAttributes="%{idp.authn.oidc.rp.deniedUserInfoAttributes:}" />
<!--
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 98c4cef..89c8eb9 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -26,8 +26,8 @@
<!-- Spring controller to start the authentication request and recieve the response -->
- <bean id="shibboleth.authn.OpenIDConnect.externalAuthnPath" class="java.lang.String"
- c:_0="servletRelative:/Authn/OIDC/RP/auth">
+ <bean id="shibboleth.authn.OIDC.externalAuthnPath" class="java.lang.String"
+ c:_0="servletRelative:/Authn/OIDC/RP/authz">
</bean>
<!-- Parent beans for indirecting into nested PRC. -->
@@ -111,6 +111,42 @@
<bean id="AddAuthzRequest" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddAuthzRequest"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+
+ <!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
+ <bean id="messageEncoderFactory"
+ class="net.shibboleth.oidc.profile.impl.AuthenticationRequestMessageEncoderFactory" scope="prototype"
+ c:encoders-ref="shibboleth.authn.oidc.rp.AuthenticationRequestEncoders" />
+
+ <!-- List must itself be a prototype so new encoders are created per request -->
+ <util:list id="shibboleth.authn.oidc.rp.AuthenticationRequestEncoders" scope="prototype">
+ <ref bean="OIDCAuthnRedirectRequestEncoder" />
+ <ref bean="OIDCAuthnPostRequestEncoder" />
+ </util:list>
+
+ <bean id="OIDCAuthnRedirectRequestEncoder"
+ class="net.shibboleth.oidc.profile.encoders.impl.HTTPRedirectEncoder" init-method="" scope="prototype"
+ p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
+
+ <bean id="OIDCAuthnPostRequestEncoder" class="net.shibboleth.oidc.profile.encoders.impl.HTTPPostEncoder"
+ init-method="" scope="prototype" p:velocityEngine-ref="shibboleth.VelocityEngine"
+ p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
+
+
+ <bean id="EncodeMessage" class="org.opensaml.profile.action.impl.EncodeMessage" scope="prototype"
+ p:messageEncoderFactory-ref="messageEncoderFactory" p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
+
+ <!-- TODO: Place holder for message handlers -->
+ <bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
+ <property name="handlers">
+ <list>
+
+ </list>
+ </property>
+ </bean>
+
+
+
+
<!-- OLD STUFF -->
@@ -154,12 +190,12 @@
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
p:cleanupHook="#{getObject('shibboleth.authn.oidc.rp.jwt.claims.CleanUpHook')
?: getObject('shibboleth.authn.oidc.rp.jwt.claims.DefaultCleanupHook')}"
- p:claimsValidator="#{getObject('shibboleth.authn.oidc.rp.DuoTokenClaimsVerifier')
- ?: getObject('shibboleth.authn.oidc.rp.DefaultDuoTokenClaimsVerifier')}" />
+ p:claimsValidator="#{getObject('shibboleth.authn.oidc.rp.RPTokenClaimsVerifier')
+ ?: getObject('shibboleth.authn.oidc.rp.DefaultRPTokenClaimsVerifier')}" />
<!-- TODO ensure these claims are correct in the general OIDC case. -->
<!-- OIDC claims verification Other claim verifications e.g. ACR and AZP-->
- <bean id="shibboleth.authn.oidc.rp.DefaultDuoTokenClaimsVerifier"
+ <bean id="shibboleth.authn.oidc.rp.DefaultRPTokenClaimsVerifier"
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
<property name="claimValidators">
<list>
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 5d0d608..c58dde7 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -18,8 +18,7 @@
<action-state id="PostDiscovery">
<!-- Nest new PRC inside AC -->
- <evaluate expression="InitializeProxyProfileRequestContext" />
-
+ <evaluate expression="InitializeProxyProfileRequestContext" />
<evaluate expression="FlowStartPopulateAuditContext" />
<!-- this is nice to get the rp out of the authenticating auth and setup a msg context
@@ -28,57 +27,35 @@
<!-- <evaluate expression="SAMLProtocolAndRole" /> maybe we need an OIDC role selector here, to say this is an
OP over the normal RP -->
- <evaluate expression="OIDCMetadataLookup" />
-
+ <evaluate expression="OIDCMetadataLookup" />
-
<evaluate expression="InitializeRelyingPartyContext" />
<evaluate expression="SelectRelyingPartyConfiguration" />
+ <!-- <evaluate expression="PostLookupPopulateAuditContext" /> -->
<evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="AddAuthzRequest"/>
-
+ <!-- <evaluate expression="PostRequestPopulateAuditContext" />
+ <evaluate expression="WriteAuditLog" /> -->
+
<!--
-
- <evaluate expression="PostLookupPopulateAuditContext" />
-
-
- <evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="InitializeMessageChannelSecurityContext" />
<evaluate expression="PopulateBindingAndEndpointContexts" />
-
- <evaluate expression="PopulateRequestSignatureSigningParameters" />
-
- <evaluate expression="AddAuthnRequest" />
- <evaluate expression="PostRequestPopulateAuditContext" />
- <evaluate expression="WriteAuditLog" /> -->
+ <evaluate expression="PopulateRequestSignatureSigningParameters" />
+ -->
<evaluate expression="'proceed'" />
<transition on="proceed" to="AuthRequest" />
</action-state>
-
-
-
- <!-- Discovery done -->
-<!--
- <action-state id="SetRPUIInformation">
- <evaluate expression="SetRPUIInformation" />
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="SetOIDCInformation" />
- </action-state>
-
- <action-state id="SetOIDCInformation">
- <evaluate expression="SetOIDCInformation" />
- <evaluate expression="'proceed'" />
- <transition on="proceed" to="ExternalTransfer" />
- </action-state> -->
<view-state id="AuthRequest"
- view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.OpenIDConnect.externalAuthnPath'), flowExecutionContext.getKey().toString())}">
+ view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.OIDC.externalAuthnPath'), flowExecutionContext.getKey().toString())}">
<attribute name="csrf_excluded" value="true" type="boolean" />
<on-render>
<evaluate
expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext(EncodeMessage), true)" result="flowScope.oidcContext" />
+ <evaluate expression="flowScope.oidcContext.setOutboundMessageHandler(PreEncodeMessageHandler)" />
</on-render>
<transition to="ValidateResponse" />
</view-state>
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
index f4b918b..fe8410a 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
@@ -81,7 +81,7 @@
class="net.shibboleth.oidc.metadata.cache.impl.DefaultOIDCProviderMetadataExpirationTimeStrategy"/>
<bean id="shibboleth.oidc.rp.DefaultODICProviderSourceMetadataExpirationTimeStrategy" scope="prototype"
- class="net.shibboleth.oidc.metadata.cache.impl.DefaultOIDCProviderSourceMetadataExpirationTimeStrategy"
+ class="net.shibboleth.oidc.metadata.cache.impl.DefaultSourceMetadataExpirationTimeStrategy"
c:duration="PT10M" />
<bean id="shibboleth.oidc.rp.DefaultOIDCProviderMetadataIdentifierExtractionStrategy" scope="prototype"
diff --git a/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm b/idp-oidc-rp-impl/src/main/resources/templates/oidc-request-form-post.vm
new file mode 100644
index 0000000..8b303e0
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/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
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
new file mode 100644
index 0000000..03006ff
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
@@ -0,0 +1,311 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLEncoder;
+
+import javax.annotation.Nonnull;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.mockito.Mockito;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.encoder.MessageEncoder;
+import org.opensaml.messaging.encoder.MessageEncodingException;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.opensaml.profile.action.AbstractProfileAction;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.ServletContextAttributeExporter;
+import org.springframework.webflow.core.collection.LocalAttributeMap;
+import org.springframework.webflow.core.collection.MutableAttributeMap;
+import org.springframework.webflow.execution.FlowExecution;
+import org.springframework.webflow.execution.repository.FlowExecutionRepository;
+import org.springframework.webflow.execution.repository.support.CompositeFlowExecutionKey;
+import org.springframework.webflow.executor.FlowExecutorImpl;
+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.idp.authn.AuthenticationFlowDescriptor;
+import net.shibboleth.idp.authn.ExternalAuthentication;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
+import net.shibboleth.idp.plugin.authn.test.flow.mock.IdPPropertyConfigurer;
+import net.shibboleth.idp.session.IdPSession;
+import net.shibboleth.idp.session.context.SessionContext;
+import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.OIDCHttpRequestMethod;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.encoders.impl.AbstractOIDCMessageEncoder;
+import net.shibboleth.oidc.profile.encoders.impl.OIDCMessageEncoder;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.net.HttpServletSupport;
+import net.shibboleth.utilities.java.support.net.URLBuilder;
+
+
+ at ContextConfiguration(classes = {AuthorizationController.class, IdPPropertyConfigurer.class})
+ at WebAppConfiguration
+ at TestPropertySource(properties = {"shibboleth.authn.OIDC.externalAuthnPath=/Authn/OIDC/RP",})
+public class AuthorizationControllerTest extends AbstractTestNGSpringContextTests {
+
+ /** The web application context loaded by the test framework. */
+ @Nonnull @Autowired private WebApplicationContext webApplicationContext;
+
+ /** The endpoint to redirect the user-agent to.*/
+ @Nonnull private final String ENDPOINT_URI = "https://op.example.com/";
+
+ /** The redirect to direct the user-agent to after successful authentication. */
+ @Nonnull private final String REDIRECT_URI = "https://rp.example.com/callback";
+
+ /** The mock MVC entry point for testing. */
+ @Nonnull private MockMvc mockMvc;
+
+ /** The mock servlet context.*/
+ @Nonnull @Autowired private ServletContext servletContext;
+
+ /** The mock http servlet response.*/
+ @Nonnull @Autowired private HttpServletResponse response;
+
+ /** The mock http servlet request.*/
+ @Nonnull @Autowired private HttpServletRequest request;
+
+ /**
+ * Setup.
+ *
+ * @throws Exception on error.
+ */
+ @BeforeMethod
+ public void setUp() throws Exception {
+
+
+ // check controller is instantiated.
+ final AuthorizationController controller = webApplicationContext.getBean(AuthorizationController.class);
+ assertNotNull(controller);
+ assertNotNull(response);
+ assertNotNull(request);
+
+ mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
+
+ //add and mock attributes of the servlet context.
+ exportServletContextAttributes();
+ }
+
+ @Test
+ public void testSuccessfulAuthorizeRequest() throws Exception {
+
+ final MvcResult result = mockMvc.perform(get("/Authn/OIDC/RP/authz")
+ .param("conversation", "e1s1").characterEncoding("UTF-8"))
+ .andDo(print())
+ .andExpect(status().is3xxRedirection()).andReturn();
+ assertNotNull(result.getResponse().getHeader("Location"));
+ final ExternalAuthenticationContext extContext = extractExternalAuthContext();
+ // assert no error in the context
+ assertNull(extContext.getAuthnError());
+ //basic check of the redirection URL.
+ assertTrue(result.getResponse().getRedirectedUrl().contains(ENDPOINT_URI));
+ assertTrue(result.getResponse().getRedirectedUrl().contains(URLEncoder.encode(REDIRECT_URI, "UTF-8")));
+ assertTrue(result.getResponse().getRedirectedUrl().contains("client_id"));
+ assertTrue(result.getResponse().getRedirectedUrl().contains("response_type"));
+ assertTrue(result.getResponse().getRedirectedUrl().contains("scope"));
+ }
+
+ /**
+ * Export the FlowExecutor to the servlet context with the correct set of configured contexts. Mimicking the
+ * IdP's configuration of the {@link ServletContextAttributeExporter}.
+ *
+ * @throws Exception on error.
+ */
+ private void exportServletContextAttributes() throws Exception {
+
+ final FlowExecutorImpl mockFlowExecutor = Mockito.mock(FlowExecutorImpl.class);
+ final FlowExecutionRepository mockFlowExecutionRepo = Mockito.mock(FlowExecutionRepository.class);
+ final FlowExecution mockFlowExecution = Mockito.mock(FlowExecution.class);
+
+ final MutableAttributeMap<Object> map = new LocalAttributeMap<Object>();
+ map.put(ProfileRequestContext.BINDING_KEY, buildProfileRequestContext());
+
+ Mockito.when(mockFlowExecutor.getExecutionRepository()).thenReturn(mockFlowExecutionRepo);
+ final CompositeFlowExecutionKey key = new CompositeFlowExecutionKey("1", "1");
+ Mockito.when(mockFlowExecutionRepo.parseFlowExecutionKey("e1s1")).thenReturn(key);
+ Mockito.when(mockFlowExecutionRepo.getFlowExecution(key)).thenReturn(mockFlowExecution);
+ Mockito.when(mockFlowExecution.getConversationScope()).thenReturn(map);
+
+ // overwrites previous if set from previous method executions.
+ servletContext.setAttribute(ExternalAuthentication.SWF_KEY, mockFlowExecutor);
+ }
+
+ /**
+ * Build a nested {@link ProfileRequestContext} by configuring a suitable context tree for external authentication e.g. a
+ * {@link AuthenticationContext} and {@link ExternalAuthenticationContext}. Nest the proxy PRC under the authentication
+ * context.
+ *
+ * @return a profile request context.
+ * @throws Exception on error creating the duo client
+ */
+ @Nonnull private ProfileRequestContext buildProfileRequestContext() throws Exception {
+
+ // Add an outer root PRC.
+ final ProfileRequestContext rootPrc = new ProfileRequestContext();
+ final AuthenticationContext ac = new AuthenticationContext();
+ rootPrc.addSubcontext(ac);
+ // Add a nested proxy PRC under the authentication context.
+ final ProfileRequestContext prc = new ProfileRequestContext();
+ ac.addSubcontext(prc);
+
+ final ExternalAuthenticationContext ec = new ExternalAuthenticationContext(new ExternalAuthenticationImpl());
+ ac.addSubcontext(ec);
+
+ final MockRedirectEncoder encoder = new MockRedirectEncoder();
+ encoder.setHttpServletResponse(response);
+ final MockEncodeMessage encode = new MockEncodeMessage(encoder);
+ encode.setHttpServletResponse(response);
+ final OIDCAuthnContext oidcContext = new OIDCAuthnContext(encode);
+
+ ac.addSubcontext(oidcContext);
+
+ // will redirect here once finished or in error.
+ ec.setFlowExecutionUrl("http://localhost/idp/profile/SSO&_eventId_proceed=1");
+
+ final AuthenticationFlowDescriptor afd = new AuthenticationFlowDescriptor();
+ afd.setId("authn/OIDCRelyingParty");
+ ac.setAttemptedFlow(afd);
+ ac.addSubcontext(new RelyingPartyUIContext());
+ ac.setForceAuthn(false);
+
+ final SubjectCanonicalizationContext scc = new SubjectCanonicalizationContext();
+ scc.setPrincipalName("jdoe");
+ final SessionContext sc = new SessionContext();
+ final IdPSession idpSession = Mockito.mock(IdPSession.class);
+ Mockito.when(idpSession.getPrincipalName()).thenReturn("jdoe");
+ sc.setIdPSession(idpSession);
+ rootPrc.addSubcontext(sc);
+ rootPrc.addSubcontext(scc);
+
+ // Create an add a build OIDCAuthenticationRequest, add to nested PRC outboundmessage
+ final OIDCAuthenticationRequest request =
+ new OIDCAuthenticationRequest(new ClientID("test-client"));
+ request.setResponseType(ResponseType.CODE);
+ request.setEndpointURI(new URI(ENDPOINT_URI));
+ request.setRedirectURI(new URI(REDIRECT_URI));
+
+ final MessageContext msgCtx = new MessageContext();
+ msgCtx.setMessage(request);
+ prc.setOutboundMessageContext(msgCtx);
+
+ return rootPrc;
+ }
+
+ private ExternalAuthenticationContext extractExternalAuthContext() {
+ final Object flowExecutorObject = servletContext.getAttribute(ExternalAuthentication.SWF_KEY);
+ assertTrue(flowExecutorObject instanceof FlowExecutorImpl);
+
+ final Object prcObject = ((FlowExecutorImpl)flowExecutorObject).getExecutionRepository().
+ getFlowExecution(new CompositeFlowExecutionKey("1", "1")).
+ getConversationScope().get(ProfileRequestContext.BINDING_KEY);
+ assertTrue(prcObject instanceof ProfileRequestContext);
+
+ final ExternalAuthenticationContext extContext = ((ProfileRequestContext)prcObject).
+ getSubcontext(AuthenticationContext.class).getSubcontext(ExternalAuthenticationContext.class);
+ assertNotNull(extContext);
+
+ return extContext;
+ }
+
+ /** Mock EncodeMessage profile action.*/
+ private class MockEncodeMessage extends AbstractProfileAction {
+
+
+ /** The message encoder to be returned by this factory. */
+ @Nonnull private MessageEncoder messageEncoder;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param encoder the encoder to use.
+ */
+ public MockEncodeMessage(final MessageEncoder encoder) {
+ messageEncoder = encoder;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ try {
+
+ if (!messageEncoder.isInitialized()) {
+ messageEncoder.setMessageContext(profileRequestContext.getOutboundMessageContext());
+ messageEncoder.initialize();
+ }
+ messageEncoder.prepareContext();
+ messageEncoder.encode();
+
+ } catch (final MessageEncodingException | ComponentInitializationException e) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.UNABLE_TO_ENCODE);
+ } finally {
+ messageEncoder.destroy();
+ }
+ }
+ }
+
+ private class MockRedirectEncoder extends AbstractOIDCMessageEncoder {
+
+ @Override
+ public boolean test(OIDCHttpRequestMethod t) {
+ return true;
+ }
+
+ @Override
+ protected void doEncode() throws MessageEncodingException {
+ try {
+
+ final MessageContext messageContext = getMessageContext();
+ final OIDCAuthenticationRequest outboundMessage = (OIDCAuthenticationRequest)messageContext.getMessage();
+
+
+ URLBuilder urlBuilder = new URLBuilder(outboundMessage.getEndpointURI().toString());
+ serializeAuthorizationParamsToUrl(outboundMessage, urlBuilder);
+ final String redirectURL = urlBuilder.buildURL();
+
+ final HttpServletResponse response = getHttpServletResponse();
+ HttpServletSupport.addNoCacheHeaders(response);
+ HttpServletSupport.setUTF8Encoding(response);
+ HttpServletSupport.setContentType(response, "application/x-www-form-urlencoded");
+ response.sendRedirect(redirectURL);
+ } catch (final IOException e) {
+ throw new MessageEncodingException("Problem sending HTTP redirect", e);
+ }
+
+ }
+
+
+
+ }
+
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index 6eebef8..ee88be0 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -30,14 +30,10 @@ import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
-import net.shibboleth.ext.spring.service.ReloadableSpringService;
import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
-import net.shibboleth.idp.relyingparty.RelyingPartyConfigurationResolver;
-import net.shibboleth.idp.relyingparty.impl.ReloadingRelyingPartyConfigurationResolver;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
-import net.shibboleth.utilities.java.support.service.ReloadableService;
/** Test the OIDC relying party flow.*/
public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
@@ -172,6 +168,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
loadBeanDefinitionsFromXmlFile(builderContext, new ClassPathResource("META-INF/net.shibboleth.idp/postconfig.xml"));
loadBeanDefinitionsFromXmlFile(builderContext, new ClassPathResource("conf/test-relyingparty-resolver-service.xml"));
+
+ loadBeanDefinitionsFromXmlFile(builderContext, new ClassPathResource("conf/additional-system-beans.xml"));
}
@@ -208,8 +206,6 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
.put("opensamlProfileRequestContext", buildProfileRequestContext("authn/OIDCRelyingParty",false,true));
updateFlowExecution(flowExecution);
flowExecution.start(inputMap, externalContext);
- //TODO: only check it has ended, should check correct state - but it does not allow this
- //find a way to check last state of an ended flow.
- assertFlowExecutionEnded();
+ assertCurrentStateEquals("AuthRequest");
}
}
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/additional-system-beans.xml b/idp-oidc-rp-impl/src/test/resources/conf/additional-system-beans.xml
new file mode 100644
index 0000000..09628b3
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/additional-system-beans.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <!-- from externalBeans.xml -->
+ <bean id="shibboleth.VelocityEngine" class="net.shibboleth.ext.spring.velocity.VelocityEngineFactoryBean">
+ <property name="velocityProperties">
+ <props>
+ <prop key="resource.loaders">classpath, string</prop>
+ <prop key="resource.loader.classpath.class">
+ org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ <prop key="resource.loader.string.class">
+ org.apache.velocity.runtime.resource.loader.StringResourceLoader
+ </prop>
+ </props>
+ </property>
+ </bean>
+
+
+</beans>
\ 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