[java-identity-provider] branch master updated: IDP-1494 - Login flow for proxied authentication
Scott Cantor
cantor.2 at osu.edu
Fri Oct 18 11:34:31 EDT 2019
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository java-identity-provider.
View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=d27b3786c02501d37ff2db4fcccd0032390eaf9c
The following commit(s) were added to refs/heads/master by this push:
new d27b378 IDP-1494 - Login flow for proxied authentication
d27b378 is described below
commit d27b3786c02501d37ff2db4fcccd0032390eaf9c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Oct 18 11:34:27 2019 -0400
IDP-1494 - Login flow for proxied authentication
https://issues.shibboleth.net/jira/browse/IDP-1494
Fix MVC URL mapping behavior.
Working proxy flow with discovery hook.
WIP on SAML subflow with request and decoding working.
---
.../idp/authn/AbstractAuthenticationAction.java | 3 +-
.../idp/authn/context/AuthenticationContext.java | 48 ++++-
.../impl/DiscoveryProfileRequestFunction.java | 148 +++++++++++++
.../authn/proxy/impl/ExtractDiscoveryResponse.java | 70 ++++++
.../impl/InitializeProxyProfileRequestContext.java | 140 ++++++++++++
.../idp/authn/proxy/impl/package-info.java | 22 ++
.../authn/spnego/impl/SPNEGOAuthnController.java | 3 +
.../impl/DiscoveryProfileRequestFunctionTest.java | 71 ++++++
.../proxy/impl/ExtractDiscoveryResponseTest.java | 69 ++++++
.../InitializeProxyProfileRequestContextTest.java | 66 ++++++
.../main/resources/conf/authn/general-authn.xml | 5 +
.../resources/conf/authn/proxy-authn-config.xml | 33 +++
.../main/resources/system/conf/audit-system.xml | 101 +++++++++
.../src/main/resources/system/conf/mvc-beans.xml | 14 +-
.../system/conf/relying-party-mddriven.xml | 3 +
.../resources/system/conf/saml-binding-config.xml | 7 +
.../src/main/resources/system/conf/utilities.xml | 15 ++
.../main/resources/system/conf/webflow-config.xml | 4 +
.../system/flows/authn/authn-abstract-flow.xml | 6 +
.../system/flows/authn/proxy-authn-beans.xml | 43 ++++
.../system/flows/authn/proxy-authn-flow.xml | 58 +++++
.../system/flows/authn/proxy/saml-proxy-beans.xml | 221 +++++++++++++++++++
.../system/flows/authn/proxy/saml-proxy-flow.xml | 60 ++++++
.../system/flows/logout/propagation/saml2-flow.xml | 2 +-
.../system/flows/saml/saml-abstract-beans.xml | 2 +-
.../system/flows/saml/saml-abstract-flow.xml | 2 +-
.../relyingparty/RelyingPartyConfiguration.java | 2 +-
.../profile/impl/WebFlowProfileActionAdaptor.java | 4 +-
.../config/AbstractSAMLProfileConfiguration.java | 2 +-
.../config/BrowserSSOProfileConfiguration.java | 50 +++++
.../impl/SpringAwareMessageDecoderFactory.java | 114 ++++++++++
.../impl/SpringAwareMessageEncoderFactory.java | 9 +-
.../profile/impl/AddAuthnRequest.java} | 178 ++++++++-------
.../saml/saml2/profile/impl/SAMLAuthnContext.java | 101 +++++++++
.../saml2/profile/impl/SAMLAuthnController.java | 239 +++++++++++++++++++++
.../profile/impl/ValidateSAMLAuthentication.java | 94 ++++++++
.../idp/saml/session/impl/AddLogoutRequest.java | 2 +-
.../session/impl/PrepareInboundMessageContext.java | 47 ++--
38 files changed, 1921 insertions(+), 137 deletions(-)
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractAuthenticationAction.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractAuthenticationAction.java
index 23cecfc..7536181 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractAuthenticationAction.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractAuthenticationAction.java
@@ -64,7 +64,8 @@ public abstract class AbstractAuthenticationAction
*
* @param strategy lookup strategy function for {@link AuthenticationContext}.
*/
- public void setLookupStrategy(@Nonnull final Function<ProfileRequestContext, AuthenticationContext> strategy) {
+ public void setAuthenticationContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,AuthenticationContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
authnCtxLookupStrategy = Constraint.isNotNull(strategy, "Strategy cannot be null");
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
index 9ebabc2..3903eab 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
@@ -79,6 +79,9 @@ public final class AuthenticationContext extends BaseContext {
/** Allowed time since an {@link AuthenticationResult} was created that it can be reused for this request. */
@Nullable private Duration maxAge;
+
+ /** Name of a proxied authentication source to use. */
+ @Nullable private String authenticatingAuthority;
/** Lookup strategy for a fixed event to return from validators for testing. */
@Nullable private Function<ProfileRequestContext,String> fixedEventLookupStrategy;
@@ -363,6 +366,36 @@ public final class AuthenticationContext extends BaseContext {
}
/**
+ * Gets the identifier or location, depending on protocol, of an authentication system to use
+ * as a proxied source of authentication.
+ *
+ * @return identifier/location of proxy authentication source
+ *
+ * @since 4.0.0
+ */
+ @Nullable public String getAuthenticatingAuthority() {
+ return authenticatingAuthority;
+ }
+
+ /**
+ * Sets the identifier or location, depending on protocol, of an authentication system to use
+ * as a proxied source of authentication.
+ *
+ * <p>Used to track the result of a hardcoded or discovery-driven determination of a
+ * third party authentication source.</p>
+ *
+ * @param authority identifier/location of proxy authentication source
+ *
+ * @return this context
+ *
+ * @since 4.0.0
+ */
+ @Nonnull public AuthenticationContext setAuthenticatingAuthority(@Nullable final String authority) {
+ authenticatingAuthority = StringSupport.trimOrNull(authority);
+ return this;
+ }
+
+ /**
* Get optional lookup strategy to return a fixed event to return from credential validation
* to exercise error and warning logic.
*
@@ -535,10 +568,9 @@ public final class AuthenticationContext extends BaseContext {
final RequestedPrincipalContext rpCtx = getSubcontext(RequestedPrincipalContext.class);
if (rpCtx != null) {
return rpCtx.isAcceptable(component);
- } else {
- // No requirements so anything is acceptable.
- return true;
}
+ // No requirements so anything is acceptable.
+ return true;
}
/**
@@ -554,10 +586,9 @@ public final class AuthenticationContext extends BaseContext {
final RequestedPrincipalContext rpCtx = getSubcontext(RequestedPrincipalContext.class);
if (rpCtx != null) {
return rpCtx.isAcceptable(principals);
- } else {
- // No requirements so anything is acceptable.
- return true;
}
+ // No requirements so anything is acceptable.
+ return true;
}
/**
@@ -574,10 +605,9 @@ public final class AuthenticationContext extends BaseContext {
final RequestedPrincipalContext rpCtx = getSubcontext(RequestedPrincipalContext.class);
if (rpCtx != null) {
return rpCtx.isAcceptable(principal);
- } else {
- // No requirements so anything is acceptable.
- return true;
}
+ // No requirements so anything is acceptable.
+ return true;
}
/**
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunction.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunction.java
new file mode 100644
index 0000000..045bfe2
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunction.java
@@ -0,0 +1,148 @@
+/*
+ * 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.authn.proxy.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.webflow.execution.RequestContext;
+
+import com.google.common.escape.Escaper;
+import com.google.common.net.UrlEscapers;
+
+/**
+ * A {@link Function} that produces a discovery request URL using the protocol defined in
+ * https://wiki.oasis-open.org/security/IdpDiscoSvcProtonProfile
+ *
+ * <p>Since there is no upstream "relying party" yet, the identity of the system is derived
+ * from the currently in-effect entityID that will be used to respond to the downstream
+ * relying party.</p>
+ */
+ at ThreadSafe
+public class DiscoveryProfileRequestFunction extends AbstractInitializableComponent
+ implements Function<Pair<RequestContext,ProfileRequestContext>,String> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DiscoveryProfileRequestFunction.class);
+
+ /** URL query parameter escaper. */
+ @Nonnull private Escaper escaper;
+
+ /** Lookup strategy for locating {@link RelyingPartyContext}. */
+ @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /** Lookup strategy for determining the "base" discovery URL. */
+ @NonnullAfterInit private Function<ProfileRequestContext,String> discoveryURLLookupStrategy;
+
+ /** Constructor. */
+ public DiscoveryProfileRequestFunction() {
+ escaper = UrlEscapers.urlFormParameterEscaper();
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+ }
+
+ /**
+ * Set the lookup strategy for the {@link RelyingPartyContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy for the "base" discovery service URL to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDiscoveryURLLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ discoveryURLLookupStrategy = Constraint.isNotNull(strategy, "Discovery URL lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (discoveryURLLookupStrategy == null) {
+ throw new ComponentInitializationException("Discovery URL lookup strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nonnull final Pair<RequestContext,ProfileRequestContext> input) {
+
+ final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.apply(input.getSecond());
+ Constraint.isNotNull(rpCtx, "RelyingPartyContext cannot be null");
+ Constraint.isNotNull(rpCtx.getConfiguration(), "RelyingPartyConfiguration cannot be null");
+
+ final String baseURL = discoveryURLLookupStrategy.apply(input.getSecond());
+ Constraint.isNotEmpty(baseURL, "Discovery URL cannot be null or empty");
+
+ final String entityID = rpCtx.getConfiguration().getResponderId(input.getSecond());
+
+ final StringBuilder builder = new StringBuilder(baseURL);
+
+ builder.append(baseURL.contains("?") ? '&' : '?').append("entityID=").append(escaper.escape(entityID));
+
+ final AuthenticationContext authenticationContext =
+ input.getSecond().getSubcontext(AuthenticationContext.class);
+ if (authenticationContext != null && authenticationContext.isPassive()) {
+ builder.append("&isPassive=true");
+ }
+
+ final HttpServletRequest httpServletRequest =
+ (HttpServletRequest) input.getFirst().getExternalContext().getNativeRequest();
+
+ final StringBuilder selfBuilder = new StringBuilder(httpServletRequest.getScheme());
+ selfBuilder.append("://").append(httpServletRequest.getServerName());
+
+ final int port = httpServletRequest.getServerPort();
+ if (port != (httpServletRequest.isSecure() ? 443 : 80)) {
+ selfBuilder.append(':').append(port);
+ }
+
+ selfBuilder.append(input.getFirst().getFlowExecutionUrl()).append("&_eventId_proceed=1");
+
+ builder.append("&return=").append(escaper.escape(selfBuilder.toString()));
+
+ return builder.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponse.java
new file mode 100644
index 0000000..a05d9c5
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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.authn.proxy.impl;
+
+import javax.annotation.Nonnull;
+import javax.servlet.http.HttpServletRequest;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that extracts a discovery service result and copies it to the {@link AuthenticationContext}.
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
+ * @post If getHttpServletRequest() != null, the content of the "entityID" parameter will be
+ * added via {@link AuthenticationContext#setAuthenticatingAuthority(String)}.
+ */
+public class ExtractDiscoveryResponse extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractDiscoveryResponse.class);
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final HttpServletRequest request = getHttpServletRequest();
+ if (request == null) {
+ log.debug("{} Profile action does not contain an HttpServletRequest", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ final String entityID = StringSupport.trimOrNull(request.getParameter("entityID"));
+
+ if (entityID == null) {
+ log.debug("{} No entityID parameter found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ authenticationContext.setAuthenticatingAuthority(entityID);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContext.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContext.java
new file mode 100644
index 0000000..1473d2a
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContext.java
@@ -0,0 +1,140 @@
+/*
+ * 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.authn.proxy.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+/**
+ * Action that creates a new {@link ProfileRequestContext} via a creation strategy,
+ * and sets the profile and logging IDs, if provided.
+ *
+ * <p>This is designed by default for use in creating a nested context tree
+ * beneath an active {@link AuthenticationContext} for use in managing a nested
+ * profile interaction with an external IdP.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @post context is created per the supplied strategy
+ */
+ at ThreadSafe
+public final class InitializeProxyProfileRequestContext extends AbstractProfileAction {
+
+ /** Context creation strategy. */
+ @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextCreationStrategy;
+
+ /** The profile ID to initialize the context to. */
+ @Nullable private String profileId;
+
+ /** The logging ID to initialize the context to. */
+ @Nullable private String loggingId;
+
+ /** Whether this is a browser-based profile request. */
+ private boolean browserProfile;
+
+ /** Constructor. */
+ public InitializeProxyProfileRequestContext() {
+
+ // Defaults to PRC -> AuthenticationContext -> PRC
+ profileRequestContextCreationStrategy =
+ new ChildContextLookup<>(ProfileRequestContext.class, true).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+ }
+
+ /**
+ * Set the strategy to use to locate/create the {@link ProfileRequestContext to operate on.
+ *
+ * @param strategy lookup/creation strategy
+ */
+ public void setProfileRequestContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,ProfileRequestContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ profileRequestContextCreationStrategy = Constraint.isNotNull(strategy, "Creation strategy cannot be null");
+ }
+
+ /**
+ * Set the profile ID to populate into the context.
+ *
+ * @param id profile ID to populate into the context
+ */
+ public void setProfileId(@Nullable final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ profileId = StringSupport.trimOrNull(id);
+ }
+
+ /**
+ * Set the logging ID to populate into the context.
+ *
+ * @param id logging ID to populate into the context
+ */
+ public void setLoggingId(@Nullable final String id) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ loggingId = StringSupport.trimOrNull(id);
+ }
+
+ /**
+ * Set whether the request is browser-based, defaults to false.
+ *
+ * @param browser true iff the request is browser based
+ */
+ public void setBrowserProfile(final boolean browser) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ browserProfile = browser;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull public void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final ProfileRequestContext prc = profileRequestContextCreationStrategy.apply(profileRequestContext);
+ if (prc == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ if (profileId != null) {
+ prc.setProfileId(profileId);
+ }
+
+ if (loggingId != null) {
+ prc.setLoggingId(loggingId);
+ }
+
+ prc.setBrowserProfile(browserProfile);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/package-info.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/package-info.java
new file mode 100644
index 0000000..01021bc
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/proxy/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes implementing proxied authentication flows.
+ */
+
+package net.shibboleth.idp.authn.proxy.impl;
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnController.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnController.java
index fb8f179..f4f97f2 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnController.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/spnego/impl/SPNEGOAuthnController.java
@@ -42,6 +42,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
+import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -56,6 +57,8 @@ import org.springframework.web.servlet.ModelAndView;
* {@link ExternalAuthentication#finishExternalAuthentication(String, HttpServletRequest, HttpServletResponse)} and
* returning null. On unrecoverable errors, an exception is thrown.
*/
+ at Controller
+ at RequestMapping(value="%{idp.authn.spnego.externalAuthnPath:/Authn/SPNEGO}")
public class SPNEGOAuthnController {
/** Event ID indicating that SPNEGO is not supported by the client or is not available for other reasons. */
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunctionTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunctionTest.java
new file mode 100644
index 0000000..f532a00
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/DiscoveryProfileRequestFunctionTest.java
@@ -0,0 +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.authn.proxy.impl;
+
+import net.shibboleth.idp.authn.impl.BaseAuthenticationContextTest;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+import net.shibboleth.utilities.java.support.collection.Pair;
+import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.net.URISupport;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.webflow.test.MockFlowExecutionContext;
+import org.springframework.webflow.test.MockFlowExecutionKey;
+import org.springframework.webflow.test.MockRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link DiscoveryProfileRequestFunction} unit test. */
+public class DiscoveryProfileRequestFunctionTest extends BaseAuthenticationContextTest {
+
+ private DiscoveryProfileRequestFunction function;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ ((MockFlowExecutionContext) ((MockRequestContext) src).getFlowExecutionContext()).setKey(new MockFlowExecutionKey("flowkey"));
+
+ function = new DiscoveryProfileRequestFunction();
+ function.setDiscoveryURLLookupStrategy(FunctionSupport.constant("https://ds.example.org/DS"));
+ function.initialize();
+ }
+
+ @Test public void test() throws Exception {
+ final URL url = new URL(function.apply(new Pair<>(src, prc)));
+
+ Assert.assertEquals(url.getProtocol(), "https");
+ Assert.assertEquals(url.getHost(), "ds.example.org");
+ Assert.assertEquals(url.getPort(), -1);
+ Assert.assertEquals(url.getPath(), "/DS");
+
+ final List<Pair<String,String>> params = URISupport.parseQueryString(url.getQuery());
+ Assert.assertEquals(params.size(), 2);
+
+ final Map<String,String> map = new HashMap<>();
+ params.forEach((p) -> map.put(p.getFirst(), p.getSecond()));
+
+ Assert.assertEquals(map.get("entityID"), ActionTestingSupport.OUTBOUND_MSG_ISSUER);
+ Assert.assertEquals(map.get("return"), "http://localhost?execution=flowkey&_eventId_proceed=1");
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponseTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponseTest.java
new file mode 100644
index 0000000..f003ad4
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/ExtractDiscoveryResponseTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.authn.proxy.impl;
+
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.impl.BaseAuthenticationContextTest;
+import net.shibboleth.idp.profile.ActionTestingSupport;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** {@link ExtractDiscoveryResponse} unit test. */
+public class ExtractDiscoveryResponseTest extends BaseAuthenticationContextTest {
+
+ private ExtractDiscoveryResponse action;
+
+ @BeforeMethod public void setUp() throws Exception {
+ super.setUp();
+
+ action = new ExtractDiscoveryResponse();
+ action.setHttpServletRequest(new MockHttpServletRequest());
+ action.initialize();
+ }
+
+ @Test public void testNoServlet() throws Exception {
+ action = new ExtractDiscoveryResponse();
+ action.initialize();
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ Assert.assertNull(prc.getSubcontext(AuthenticationContext.class).getAuthenticatingAuthority());
+ }
+
+ @Test public void testFailure() throws Exception {
+ final Event event = action.execute(src);
+
+ ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
+ Assert.assertNull(prc.getSubcontext(AuthenticationContext.class).getAuthenticatingAuthority());
+ }
+
+ @Test public void testSuccess() throws Exception {
+ ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("entityID", "foo");
+
+ final Event event = action.execute(src);
+ ActionTestingSupport.assertProceedEvent(event);
+ Assert.assertEquals(prc.getSubcontext(AuthenticationContext.class).getAuthenticatingAuthority(), "foo");
+ }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContextTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContextTest.java
new file mode 100644
index 0000000..134de8d
--- /dev/null
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/proxy/impl/InitializeProxyProfileRequestContextTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.authn.proxy.impl;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.opensaml.profile.action.ActionTestingSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/** Unit test for {@link InitializeProxyProfileRequestContext}. */
+public class InitializeProxyProfileRequestContextTest {
+
+ private ProfileRequestContext prc;
+
+ private InitializeProxyProfileRequestContext action;
+
+ @BeforeMethod
+ public void setUp() throws ComponentInitializationException {
+ prc = new RequestContextBuilder().buildProfileRequestContext();
+
+ action = new InitializeProxyProfileRequestContext();
+ action.setProfileId("nested");
+ action.initialize();
+ }
+
+ @Test public void testFailure() throws Exception {
+
+ action.execute(prc);
+ ActionTestingSupport.assertEvent(prc, EventIds.INVALID_PROFILE_CTX);
+ }
+
+ @Test public void testSuccess() throws Exception {
+
+ prc.addSubcontext(new AuthenticationContext());
+
+ action.execute(prc);
+ ActionTestingSupport.assertProceedEvent(prc);
+
+ final ProfileRequestContext prc2 =
+ prc.getSubcontext(AuthenticationContext.class).getSubcontext(ProfileRequestContext.class);
+ Assert.assertNotNull(prc2);
+ Assert.assertEquals(prc2.getProfileId(), "nested");
+ }
+
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/conf/authn/general-authn.xml b/idp-conf/src/main/resources/conf/authn/general-authn.xml
index 5699022..906db6c 100644
--- a/idp-conf/src/main/resources/conf/authn/general-authn.xml
+++ b/idp-conf/src/main/resources/conf/authn/general-authn.xml
@@ -134,6 +134,11 @@
</property>
</bean>
+ <bean id="authn/Proxy" parent="shibboleth.AuthenticationFlow"
+ p:nonBrowserSupported="false"
+ p:passiveAuthenticationSupported="true"
+ p:forcedAuthenticationSupported="true" />
+
</util:list>
<!--
diff --git a/idp-conf/src/main/resources/conf/authn/proxy-authn-config.xml b/idp-conf/src/main/resources/conf/authn/proxy-authn-config.xml
new file mode 100644
index 0000000..531234c
--- /dev/null
+++ b/idp-conf/src/main/resources/conf/authn/proxy-authn-config.xml
@@ -0,0 +1,33 @@
+<?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">
+
+ <!-- General Configuration -->
+
+ <!-- Optional Function<ProfileRequest,String> to supply name of proxied IdP. -->
+
+ <bean id="shibboleth.authn.Proxy.discoveryFunction" parent="shibboleth.Functions.Constant"
+ c:target="urn:mace:incommon:osu.edu" />
+
+ <!-- Alternatively use a standardized discovery service. -->
+
+ <!--
+ <bean id="shibboleth.authn.Proxy.discoveryURL" class="java.lang.String"
+ c:_0="https://ds.example.org/shibboleth-ds/index.html" />
+ -->
+
+ <util:list id="shibboleth.authn.Proxy.precedence">
+ <value>SAML</value>
+ </util:list>
+
+</beans>
diff --git a/idp-conf/src/main/resources/system/conf/audit-system.xml b/idp-conf/src/main/resources/system/conf/audit-system.xml
index 599c359..3002f17 100644
--- a/idp-conf/src/main/resources/system/conf/audit-system.xml
+++ b/idp-conf/src/main/resources/system/conf/audit-system.xml
@@ -223,6 +223,107 @@
</map>
</property>
</bean>
+
+ <bean id="shibboleth.DefaultPostRequestAuditExtractors" lazy-init="true"
+ class="org.springframework.beans.factory.config.MapFactoryBean">
+ <property name="sourceMap">
+ <map>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.PROTOCOL"/>
+ </key>
+ <bean parent="shibboleth.Functions.Constant"
+ c:target-ref="shibboleth.MetadataLookup.Protocol" />
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.REQUEST_BINDING"/>
+ </key>
+ <bean parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <bean class="org.opensaml.saml.common.messaging.context.navigate.SAMLBindingContextBindingFunction"
+ p:useShortName="%{idp.audit.shortenBindings:false}" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.SAMLBindingContext"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ </constructor-arg>
+ </bean>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.SIGNING"/>
+ </key>
+ <bean parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <bean class="net.shibboleth.utilities.java.support.logic.FunctionSupport" factory-method="forPredicate">
+ <constructor-arg>
+ <bean class="net.shibboleth.ext.spring.util.SpringExpressionPredicate"
+ c:_0="#input != null ? T(org.opensaml.saml.common.binding.SAMLBindingSupport).isMessageSigned(#input) : false" />
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Outbound"/>
+ </bean>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.REQUEST_ID"/>
+ </key>
+ <bean parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <bean class="org.opensaml.saml.common.messaging.context.navigate.SAMLMessageInfoContextIDFunction" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookupOrCreate.SAMLMessageInfoContext"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ </constructor-arg>
+ </bean>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.REQUEST_ISSUE_INSTANT"/>
+ </key>
+ <bean parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <bean class="org.opensaml.saml.common.messaging.context.navigate.SAMLMessageInfoContextIssueInstantFunction" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookupOrCreate.SAMLMessageInfoContext"
+ c:f-ref="shibboleth.MessageContextLookup.Inbound" />
+ </constructor-arg>
+ </bean>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.IS_PASSIVE"/>
+ </key>
+ <bean class="net.shibboleth.idp.saml.audit.impl.IsPassiveAuditExtractor">
+ <constructor-arg>
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.MessageLookup.AuthnRequest"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ </constructor-arg>
+ </bean>
+ </entry>
+ <entry>
+ <key>
+ <util:constant static-field="net.shibboleth.idp.saml.profile.SAMLAuditFields.FORCE_AUTHN"/>
+ </key>
+ <bean class="net.shibboleth.idp.saml.audit.impl.ForceAuthnAuditExtractor">
+ <constructor-arg>
+ <bean parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.MessageLookup.AuthnRequest"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+ </constructor-arg>
+ </bean>
+ </entry>
+ </map>
+ </property>
+ </bean>
<bean id="shibboleth.DefaultPostLookupAuditExtractors" lazy-init="true"
class="org.springframework.beans.factory.config.MapFactoryBean">
diff --git a/idp-conf/src/main/resources/system/conf/mvc-beans.xml b/idp-conf/src/main/resources/system/conf/mvc-beans.xml
index 036de12..8b59e39 100644
--- a/idp-conf/src/main/resources/system/conf/mvc-beans.xml
+++ b/idp-conf/src/main/resources/system/conf/mvc-beans.xml
@@ -36,17 +36,6 @@
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" p:order="2" />
- <!-- Add this one to mount any additional MVC controllers we create. -->
-
- <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" p:order="3">
- <property name="mappings">
- <props>
- <prop key="%{idp.authn.spnego.externalAuthnPath:/Authn/SPNEGO}/**">shibboleth.SPNEGOAuthnController</prop>
- <prop key="/RaiseError">shibboleth.RaiseErrorController</prop>
- </props>
- </property>
- </bean>
-
<!-- HandlerAdapters: These aren't ordered. Add our WebFlow one, then the standard Spring MVC default ones.
Have to enumerate them all explicitly b/c we're overriding the built-in defaults. -->
@@ -63,9 +52,10 @@
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" />
- <!-- Additional MVC controllers -->
+ <!-- Additional MVC controllers scanned for annotations. -->
<bean id="shibboleth.SPNEGOAuthnController" class="net.shibboleth.idp.authn.spnego.impl.SPNEGOAuthnController" />
+ <bean id="shibboleth.SAMLAuthnPOSTController" class="net.shibboleth.idp.saml.saml2.profile.impl.SAMLAuthnController" />
<bean id="shibboleth.RaiseErrorController" class="net.shibboleth.ext.spring.error.ErrorRaisingController" />
<!-- Exception handling -->
diff --git a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
index e30f884..fa60b06 100644
--- a/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
+++ b/idp-conf/src/main/resources/system/conf/relying-party-mddriven.xml
@@ -392,6 +392,9 @@
<constructor-arg value="false" />
</bean>
</property>
+ <property name="authnContextComparisonLookupStrategy">
+ <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="authnContextComparison" />
+ </property>
<property name="defaultAuthenticationMethodsLookupStrategy">
<bean parent="shibboleth.MDDrivenListProperty" p:propertyName="defaultAuthenticationMethods"
p:propertyType="#{T(net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal)}" />
diff --git a/idp-conf/src/main/resources/system/conf/saml-binding-config.xml b/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
index 6ee5272..4ebcfcd 100644
--- a/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
+++ b/idp-conf/src/main/resources/system/conf/saml-binding-config.xml
@@ -95,6 +95,13 @@
<ref bean="shibboleth.Binding.SAML2Artifact" />
</util:list>
+ <util:list id="shibboleth.OutgoingSAML2SSORequestBindings">
+ <ref bean="shibboleth.Binding.SAML2Redirect" />
+ <ref bean="shibboleth.Binding.SAML2POST" />
+ <ref bean="shibboleth.Binding.SAML2POSTSimpleSign" />
+ <ref bean="shibboleth.Binding.SAML2Artifact" />
+ </util:list>
+
<util:list id="shibboleth.OutgoingSAML2SLOFrontBindings">
<ref bean="shibboleth.Binding.SAML2Redirect" />
<ref bean="shibboleth.Binding.SAML2POST" />
diff --git a/idp-conf/src/main/resources/system/conf/utilities.xml b/idp-conf/src/main/resources/system/conf/utilities.xml
index f11b383..eb4fa6e 100644
--- a/idp-conf/src/main/resources/system/conf/utilities.xml
+++ b/idp-conf/src/main/resources/system/conf/utilities.xml
@@ -146,6 +146,9 @@
c:type="#{ T(net.shibboleth.idp.ui.context.RelyingPartyUIContext) }"
c:createContext="true" />
+ <bean id="shibboleth.ChildLookup.ProfileRequestContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(org.opensaml.profile.context.ProfileRequestContext) }" />
<bean id="shibboleth.ChildLookup.AttributeContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
c:type="#{ T(net.shibboleth.idp.attribute.context.AttributeContext) }" />
@@ -191,6 +194,18 @@
c:type="#{ T(org.opensaml.saml.common.messaging.context.SAMLMessageInfoContext) }"
c:createContext="true" />
+ <!-- Wiring for proxy login flows to locate nested PRC. -->
+ <bean id="shibboleth.ChildLookup.ProxyProfileRequestContext" parent="shibboleth.Functions.Compose">
+ <constructor-arg>
+ <bean parent="shibboleth.Functions.Compose" c:g-ref="shibboleth.ChildLookup.ProfileRequestContext"
+ c:f-ref="shibboleth.ChildLookup.AuthenticationContext" />
+ </constructor-arg>
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup" />
+ </constructor-arg>
+ </bean>
+
+
<bean id="shibboleth.ResponderIdLookup.Simple"
class="net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction" />
diff --git a/idp-conf/src/main/resources/system/conf/webflow-config.xml b/idp-conf/src/main/resources/system/conf/webflow-config.xml
index 7f75dd4..d3f5c66 100644
--- a/idp-conf/src/main/resources/system/conf/webflow-config.xml
+++ b/idp-conf/src/main/resources/system/conf/webflow-config.xml
@@ -86,6 +86,10 @@
<entry key="authn/Duo" value="../system/flows/authn/duo-authn-flow.xml" />
<entry key="authn/MFA" value="../system/flows/authn/mfa-authn-flow.xml" />
<entry key="authn/Function" value="../system/flows/authn/function-authn-flow.xml" />
+ <entry key="authn/Proxy" value="../system/flows/authn/proxy-authn-flow.xml" />
+
+ <!-- Proxied login methods. -->
+ <entry key="proxy/SAML" value="../system/flows/authn/proxy/saml-proxy-flow.xml" />
<!-- Master flow for subject c14n. -->
<entry key="c14n.events" value="../conf/c14n/subject-c14n-events-flow.xml" />
diff --git a/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml b/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
index afe3f0e..3db09fb 100644
--- a/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
@@ -23,6 +23,9 @@
<end-state id="InvalidCredentials" />
<end-state id="InvalidEvent" />
<end-state id="InvalidProfileContext" />
+ <end-state id="InvalidProfileConfiguration" />
+ <end-state id="InvalidRelyingPartyConfiguration" />
+ <end-state id="InvalidRelyingPartyContext" />
<end-state id="InvalidSubjectContext" />
<end-state id="InvalidSubjectCanonicalizationContext" />
<end-state id="NoCredentials" />
@@ -51,6 +54,9 @@
<transition on="InputOutputError" to="InputOutputError" />
<transition on="InvalidCredentials" to="InvalidCredentials" />
<transition on="InvalidProfileContext" to="InvalidProfileContext" />
+ <transition on="InvalidProfileConfiguration" to="InvalidProfileConfiguration" />
+ <transition on="InvalidRelyingPartyConfiguration" to="InvalidRelyingPartyConfiguration" />
+ <transition on="InvalidRelyingPartyContext" to="InvalidRelyingPartyContext" />
<transition on="InvalidSubjectContext" to="InvalidSubjectContext" />
<transition on="InvalidSubjectCanonicalizationContext" to="InvalidSubjectCanonicalizationContext" />
<transition on="NoCredentials" to="NoCredentials" />
diff --git a/idp-conf/src/main/resources/system/flows/authn/proxy-authn-beans.xml b/idp-conf/src/main/resources/system/flows/authn/proxy-authn-beans.xml
new file mode 100644
index 0000000..57d35e2
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/authn/proxy-authn-beans.xml
@@ -0,0 +1,43 @@
+<?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">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />
+ <bean class="net.shibboleth.idp.profile.impl.ProfileActionBeanPostProcessor" />
+
+ <!-- Default strategy function to obtain the external path. -->
+ <bean id="shibboleth.authn.Proxy.discoveryURLStrategy" parent="shibboleth.Functions.Constant" lazy-init="true"
+ c:target-ref="shibboleth.authn.Proxy.discoveryURL" />
+
+
+
+ <import resource="../../../conf/authn/proxy-authn-config.xml" />
+
+ <bean id="DiscoveryProfileRequestFunction" lazy-init="true"
+ class="net.shibboleth.idp.authn.proxy.impl.DiscoveryProfileRequestFunction"
+ p:discoveryURLLookupStrategy-ref="shibboleth.authn.Proxy.discoveryURLStrategy" />
+
+ <!-- Action beans. -->
+
+ <bean id="ExtractDiscoveryResponse"
+ class="net.shibboleth.idp.authn.proxy.impl.ExtractDiscoveryResponse" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+
+ <bean id="PopulateSubjectCanonicalizationContext"
+ class="net.shibboleth.idp.authn.impl.PopulateSubjectCanonicalizationContext" scope="prototype"
+ p:availableFlows-ref="shibboleth.PostLoginSubjectCanonicalizationFlows" />
+
+</beans>
diff --git a/idp-conf/src/main/resources/system/flows/authn/proxy-authn-flow.xml b/idp-conf/src/main/resources/system/flows/authn/proxy-authn-flow.xml
new file mode 100644
index 0000000..ab83bfe
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/authn/proxy-authn-flow.xml
@@ -0,0 +1,58 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="authn.abstract">
+
+ <!-- This is a login flow for oroxied authentication implemented via protocol-specific subflows. -->
+
+ <on-start>
+ <set name="flowScope.protocolIterator" value="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.Proxy.precedence').iterator()" />
+ <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().containsBean('shibboleth.authn.Proxy.discoveryFunction') ? opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).setAuthenticatingAuthority(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.Proxy.discoveryFunction').apply(opensamlProfileRequestContext)) : null" />
+ </on-start>
+
+ <decision-state id="CheckDiscoveryDone">
+ <if test="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getAuthenticatingAuthority() == null"
+ then="DoDiscovery"
+ else="SelectProxyFlow" />
+ </decision-state>
+
+ <view-state id="DoDiscovery" view="externalRedirect:#{DiscoveryProfileRequestFunction.apply(new net.shibboleth.utilities.java.support.collection.Pair(flowRequestContext, opensamlProfileRequestContext))}">
+ <transition on="proceed" to="ExtractDiscoveryResponse" />
+ </view-state>
+
+ <action-state id="ExtractDiscoveryResponse">
+ <evaluate expression="ExtractDiscoveryResponse" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="SelectProxyFlow" />
+ <transition to="ReselectFlow" />
+ </action-state>
+
+ <decision-state id="SelectProxyFlow">
+ <if test="protocolIterator.hasNext()" then="CallProxyFlow" else="ReselectFlow" />
+ </decision-state>
+
+ <subflow-state id="CallProxyFlow" subflow="proxy/#{protocolIterator.next()}">
+ <input name="calledAsSubflow" value="true" />
+ <transition on="proceed" to="ContinueSuccessfulAuthentication" />
+ <transition on="ReselectFlow" to="SelectProxyFlow" />
+ </subflow-state>
+
+ <action-state id="ContinueSuccessfulAuthentication">
+ <evaluate expression="PopulateSubjectCanonicalizationContext" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="CallSubjectCanonicalization" />
+ </action-state>
+
+ <!-- This runs a c14n step on the result of the authentication. -->
+ <subflow-state id="CallSubjectCanonicalization" subflow="c14n">
+ <input name="calledAsSubflow" value="true" />
+ <transition on="proceed" to="proceed" />
+
+ <!-- This shouldn't generally happen, but if c14n fails, it's allowable to fall through. -->
+ <transition on="SubjectCanonicalizationError" to="ReselectFlow" />
+ </subflow-state>
+
+ <bean-import resource="proxy-authn-beans.xml" />
+</flow>
diff --git a/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-beans.xml b/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-beans.xml
new file mode 100644
index 0000000..547120c
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-beans.xml
@@ -0,0 +1,221 @@
+<?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">
+
+ <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"
+ p:placeholderPrefix="%{" p:placeholderSuffix="}" />
+
+ <bean class="net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor" />
+
+ <bean id="shibboleth.authn.SAML.externalAuthnPath" class="java.lang.String"
+ c:_0="servletRelative:%{idp.authn.saml.externalAuthnPath:/Authn/SAML2/POST/SSO}" />
+
+ <!-- Default strategy function to obtain the external path. -->
+ <bean id="shibboleth.authn.SAML.externalAuthnPathStrategy" parent="shibboleth.Functions.Constant"
+ c:target-ref="shibboleth.authn.SAML.externalAuthnPath" />
+
+<!-- <import resource="../../../../conf/authn/proxy/saml-proxy-config.xml" /> -->
+
+ <!-- Parent beans for indirecting into nested PRC. -->
+
+ <bean id="NestedWebFlowMessageHandlerAdaptor" abstract="true"
+ class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="NestedWebFlowProfileActionAdaptor" abstract="true"
+ class="net.shibboleth.idp.profile.impl.WebFlowProfileActionAdaptor" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="ParentAuthenticiationContextLookup" class="org.opensaml.messaging.context.navigate.ParentContextLookup"
+ c:type="net.shibboleth.idp.authn.context.AuthenticationContext" />
+
+
+ <!-- Action beans. -->
+
+ <!-- Explicitly wrapped by a non-nested action adaptor. -->
+ <bean id="InitializeProxyProfileRequestContext"
+ class="net.shibboleth.idp.authn.proxy.impl.InitializeProxyProfileRequestContext"
+ p:profileId="#{T(net.shibboleth.idp.saml.saml2.profile.config.BrowserSSOProfileConfiguration).PROFILE_ID}"
+ p:loggingId="%{idp.service.logging.saml2sso:SSO}"
+ p:browserProfile="true" />
+
+ <bean id="FlowStartPopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
+ p:fieldExtractors="#{getObject('shibboleth.FlowStartAuditExtractors') ?: getObject('shibboleth.DefaultFlowStartAuditExtractors')}" />
+
+ <!-- Declares audit field extractor functions injected into beans that populate the audit context. -->
+ <import resource="../../../conf/audit-system.xml" />
+
+ <bean id="PrepareInboundMessageContext"
+ class="net.shibboleth.idp.saml.session.impl.PrepareInboundMessageContext" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext">
+ <property name="relyingPartyLookupStrategy">
+ <bean parent="shibboleth.Functions.Compose" c:f-ref="ParentAuthenticiationContextLookup">
+ <constructor-arg name="g">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input.getAuthenticatingAuthority()" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="SAMLProtocolAndRole" parent="NestedWebFlowMessageHandlerAdaptor" c:executionDirection="INBOUND">
+ <constructor-arg name="messageHandler">
+ <bean class="org.opensaml.saml.common.binding.impl.SAMLProtocolAndRoleHandler" scope="prototype"
+ p:protocol-ref="shibboleth.MetadataLookup.Protocol"
+ p:role-ref="shibboleth.MetadataLookup.Role" />
+ </constructor-arg>
+ </bean>
+
+ <util:constant id="shibboleth.MetadataLookup.Protocol"
+ static-field="org.opensaml.saml.common.xml.SAMLConstants.SAML20P_NS" />
+
+ <util:constant id="shibboleth.MetadataLookup.Role"
+ static-field="org.opensaml.saml.saml2.metadata.IDPSSODescriptor.DEFAULT_ELEMENT_NAME" />
+
+ <bean id="SAMLMetadataLookup" parent="NestedWebFlowMessageHandlerAdaptor" c:executionDirection="INBOUND">
+ <constructor-arg name="messageHandler">
+ <bean class="org.opensaml.saml.common.binding.impl.SAMLMetadataLookupHandler" scope="prototype">
+ <property name="roleDescriptorResolver">
+ <bean class="org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver"
+ c:mdResolver-ref="shibboleth.MetadataResolver" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+ <bean id="InitializeRelyingPartyContextFromSAMLPeer"
+ class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="SelectRelyingPartyConfiguration"
+ class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:relyingPartyConfigurationResolver-ref="shibboleth.RelyingPartyConfigurationResolver" />
+
+ <bean id="PostLookupPopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:fieldExtractors="#{getObject('shibboleth.PostLookupAuditExtractors') ?: getObject('shibboleth.DefaultPostLookupAuditExtractors')}" />
+
+ <bean id="SelectProfileConfiguration"
+ class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="InitializeOutboundMessageContext"
+ class="net.shibboleth.idp.saml.profile.impl.InitializeOutboundMessageContext" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+ <bean id="InitializeMessageChannelSecurityContext" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="org.opensaml.profile.action.impl.StaticMessageChannelSecurity"
+ p:confidentialityActive="false" p:integrityActive="false" />
+ </constructor-arg>
+ </bean>
+
+ <util:constant id="shibboleth.EndpointType"
+ static-field="org.opensaml.saml.saml2.metadata.SingleSignOnService.DEFAULT_ELEMENT_NAME" />
+
+ <bean id="shibboleth.OutgoingBindingsLookupStrategy"
+ class="net.shibboleth.utilities.java.support.logic.FunctionSupport" factory-method="constant"
+ c:_0-ref="shibboleth.OutgoingSAML2SSORequestBindings" />
+
+ <bean id="PopulateBindingAndEndpointContexts"
+ class="net.shibboleth.idp.saml.profile.impl.PopulateBindingAndEndpointContexts" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:endpointResolver-ref="shibboleth.EndpointResolver"
+ p:endpointType-ref="shibboleth.EndpointType"
+ p:bindingDescriptorsLookupStrategy-ref="shibboleth.OutgoingBindingsLookupStrategy"
+ p:artifactImpliesSecureChannel="%{idp.artifact.secureChannel:true}" />
+
+ <bean id="PopulateRequestSignatureSigningParameters" parent="NestedWebFlowProfileActionAdaptor" scope="prototype">
+ <constructor-arg>
+ <bean class="org.opensaml.saml.common.profile.impl.PopulateSignatureSigningParameters"
+ p:configurationLookupStrategy-ref="shibboleth.SignatureSigningConfigurationLookup"
+ p:signatureSigningParametersResolver-ref="shibboleth.SignatureSigningParametersResolver"
+ p:noResultIsError="false">
+ <property name="activationCondition">
+ <bean class="net.shibboleth.idp.saml.profile.config.logic.SignRequestsPredicate" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+ <bean id="AddAuthnRequest"
+ class="net.shibboleth.idp.saml.saml2.profile.impl.AddAuthnRequest" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:overwriteExisting="true"
+ p:issuerLookupStrategy-ref="shibboleth.ResponderIdLookup.Simple">
+ <property name="identifierGeneratorLookupStrategy">
+ <bean class="net.shibboleth.idp.profile.config.navigate.IdentifierGenerationStrategyLookupFunction"
+ p:defaultIdentifierGenerationStrategy-ref="shibboleth.DefaultIdentifierGenerationStrategy" />
+ </property>
+ </bean>
+
+ <bean id="PostRequestPopulateAuditContext" parent="shibboleth.AbstractPopulateAuditContext"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:fieldExtractors="#{getObject('shibboleth.PostRequestAuditExtractors') ?: getObject('shibboleth.DefaultPostRequestAuditExtractors')}" />
+
+ <bean id="WriteAuditLog" class="net.shibboleth.idp.profile.audit.impl.WriteAuditLog" scope="prototype"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:formattingMap-ref="shibboleth.AuditFormattingMap"
+ p:dateTimeFormat="#{getObject('shibboleth.AuditDateTimeFormat')}"
+ p:useDefaultTimeZone="#{getObject('shibboleth.AuditDefaultTimeZone') ?: false}"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+
+ <!-- These beans are stashed into the context tree to be picked up and run outside SWF by the SAML controller. -->
+
+ <bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
+ <property name="handlers">
+ <list>
+ <bean class="org.opensaml.saml.common.binding.impl.SAMLOutboundDestinationHandler" scope="prototype"/>
+ <bean class="org.opensaml.saml.common.binding.security.impl.EndpointURLSchemeSecurityHandler" scope="prototype"/>
+ <bean class="org.opensaml.saml.common.binding.security.impl.SAMLOutboundProtocolMessageSigningHandler" scope="prototype"
+ p:signErrorResponses="%{idp.errors.signed:true}">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.NOT">
+ <constructor-arg>
+ <bean class="org.opensaml.saml.common.messaging.logic.SignatureCapableBindingPredicate" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="messageEncoderFactory" class="net.shibboleth.idp.saml.profile.impl.SpringAwareMessageEncoderFactory" />
+
+ <bean id="EncodeMessage" class="org.opensaml.profile.action.impl.EncodeMessage" scope="prototype"
+ p:messageEncoderFactory-ref="messageEncoderFactory"
+ p:messageHandler="#{getObject('shibboleth.BindingSpecificMessageHandler')}" />
+
+ <!-- This is mostly a placeholder in case we get forced to support other bindings at some point. -->
+
+ <bean id="messageDecoderFactory" class="net.shibboleth.idp.saml.profile.impl.SpringAwareMessageDecoderFactory">
+ <property name="beanMappings">
+ <map>
+ <entry key="POST" value="SAMLAuthnPostDecoder" />
+ </map>
+ </property>
+ </bean>
+
+ <bean id="SAMLAuthnPostDecoder"
+ class="org.opensaml.saml.saml2.binding.decoding.impl.HTTPPostDecoder" scope="prototype" init-method=""
+ p:parserPool-ref="shibboleth.ParserPool"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+ p:bindingDescriptor-ref="shibboleth.Binding.SAML2POST" />
+
+ <bean id="ValidateSAMLAuthentication"
+ class="net.shibboleth.idp.saml.saml2.profile.impl.ValidateSAMLAuthentication" scope="prototype"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+
+</beans>
diff --git a/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-flow.xml b/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-flow.xml
new file mode 100644
index 0000000..9991204
--- /dev/null
+++ b/idp-conf/src/main/resources/system/flows/authn/proxy/saml-proxy-flow.xml
@@ -0,0 +1,60 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="authn.abstract">
+
+ <!-- This is a login flow for oroxied authentication implemented via SAML 2.0. -->
+
+ <action-state id="IssueRequest">
+ <evaluate expression="InitializeProxyProfileRequestContext" />
+ <evaluate expression="FlowStartPopulateAuditContext" />
+
+ <evaluate expression="PrepareInboundMessageContext" />
+ <evaluate expression="SAMLProtocolAndRole" />
+ <evaluate expression="SAMLMetadataLookup" />
+
+ <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
+ <evaluate expression="SelectRelyingPartyConfiguration" />
+
+ <evaluate expression="PostLookupPopulateAuditContext" />
+
+ <evaluate expression="SelectProfileConfiguration" />
+
+ <evaluate expression="InitializeOutboundMessageContext" />
+ <evaluate expression="InitializeMessageChannelSecurityContext" />
+ <evaluate expression="PopulateBindingAndEndpointContexts" />
+
+ <evaluate expression="PopulateRequestSignatureSigningParameters" />
+
+ <evaluate expression="AddAuthnRequest" />
+ <evaluate expression="PostRequestPopulateAuditContext" />
+ <evaluate expression="WriteAuditLog" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="EncodeMessage" />
+ </action-state>
+
+ <view-state id="EncodeMessage" view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.SAML.externalAuthnPathStrategy').apply(opensamlProfileRequestContext) + '/start', flowExecutionContext.getKey().toString())}">
+ <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.saml.saml2.profile.impl.SAMLAuthnContext(EncodeMessage, messageDecoderFactory))" result="flowScope.samlContext" />
+ <evaluate expression="flowScope.samlContext.setOutboundMessageHandler(PreEncodeMessageHandler)" />
+ </on-render>
+
+ <transition on="proceed" to="ProcessResponse" />
+ </view-state>
+
+ <action-state id="ProcessResponse">
+ <evaluate expression="ValidateSAMLAuthentication" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="proceed" />
+ </action-state>
+
+ <global-transitions>
+ <!-- Route everything out as a graceful failure to allow subsequent options to run. -->
+ <transition on="#{!'proceed'.equals(currentEvent.id)}" to="ReselectFlow" />
+ </global-transitions>
+
+ <bean-import resource="saml-proxy-beans.xml" />
+</flow>
diff --git a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
index 56cf2af..9d54e5e 100644
--- a/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/logout/propagation/saml2-flow.xml
@@ -12,7 +12,7 @@
<evaluate expression="SAMLProtocolAndRole" />
<evaluate expression="SAMLMetadataLookup" />
- <evaluate expression="InitializeRelyingPartyContexFromSAMLPeer" />
+ <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
<evaluate expression="SelectRelyingPartyConfiguration" />
<evaluate expression="PostLookupPopulateAuditContext" />
diff --git a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
index 4cf6c8b..2ce289e 100644
--- a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-beans.xml
@@ -89,7 +89,7 @@
class="net.shibboleth.idp.saml.saml2.profile.impl.MapRequestedAttributesInAttributeConsumingService"
p:transcoderRegistry-ref="shibboleth.AttributeRegistryService" />
- <bean id="InitializeRelyingPartyContexFromSAMLPeer"
+ <bean id="InitializeRelyingPartyContextFromSAMLPeer"
class="net.shibboleth.idp.saml.profile.impl.InitializeRelyingPartyContextFromSAMLPeer" scope="prototype" />
<bean id="SelectRelyingPartyConfiguration"
diff --git a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
index b1e3b22..0ded5b7 100644
--- a/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/saml/saml-abstract-flow.xml
@@ -20,7 +20,7 @@
<evaluate expression="SAMLPopulateAttributeConsumingService" />
<evaluate expression="MapRequestedAttributesInAttributeConsumingService" />
- <evaluate expression="InitializeRelyingPartyContexFromSAMLPeer" />
+ <evaluate expression="InitializeRelyingPartyContextFromSAMLPeer" />
<evaluate expression="SelectRelyingPartyConfiguration" />
<evaluate expression="PostLookupPopulateAuditContext" />
diff --git a/idp-profile-api/src/main/java/net/shibboleth/idp/relyingparty/RelyingPartyConfiguration.java b/idp-profile-api/src/main/java/net/shibboleth/idp/relyingparty/RelyingPartyConfiguration.java
index f1c7ff5..2559014 100644
--- a/idp-profile-api/src/main/java/net/shibboleth/idp/relyingparty/RelyingPartyConfiguration.java
+++ b/idp-profile-api/src/main/java/net/shibboleth/idp/relyingparty/RelyingPartyConfiguration.java
@@ -82,7 +82,7 @@ public class RelyingPartyConfiguration extends AbstractIdentifiableInitializable
* @return ID to use when responding
*/
@Nonnull @NotEmpty public String getResponderId(@Nullable final ProfileRequestContext profileRequestContext) {
- return Constraint.isNotNull(responderIdLookupStrategy.apply(profileRequestContext),
+ return Constraint.isNotEmpty(responderIdLookupStrategy.apply(profileRequestContext),
"ResponderId cannot be null");
}
diff --git a/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/impl/WebFlowProfileActionAdaptor.java b/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/impl/WebFlowProfileActionAdaptor.java
index 563a2e1..37818f7 100644
--- a/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/impl/WebFlowProfileActionAdaptor.java
+++ b/idp-profile-impl/src/main/java/net/shibboleth/idp/profile/impl/WebFlowProfileActionAdaptor.java
@@ -33,7 +33,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
*/
@ThreadSafe
public class WebFlowProfileActionAdaptor extends AbstractProfileAction {
-
+
/** A POJO bean being adapted. */
@Nonnull private final ProfileAction action;
@@ -48,7 +48,7 @@ public class WebFlowProfileActionAdaptor extends AbstractProfileAction {
/** {@inheritDoc} */
@Override
- @Nonnull public void execute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ public void execute(@Nonnull final ProfileRequestContext profileRequestContext) {
action.execute(profileRequestContext);
}
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
index d5e1da6..9ea38aa 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/profile/config/AbstractSAMLProfileConfiguration.java
@@ -57,7 +57,7 @@ public abstract class AbstractSAMLProfileConfiguration extends AbstractCondition
/** Predicate used to determine if the generated request should be signed. Default returns false. */
@Nonnull private Predicate<ProfileRequestContext> signRequestsPredicate;
- /** Predicate used to determine if the generated response should be signed. Default returns true. */
+ /** Predicate used to determine if the generated response should be signed. Default returns false. */
@Nonnull private Predicate<ProfileRequestContext> signResponsesPredicate;
/** Predicate used to determine if the generated assertion should be signed. Default returns false. */
diff --git a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
index 1e37e34..8e79e42 100644
--- a/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
+++ b/idp-saml-api/src/main/java/net/shibboleth/idp/saml/saml2/profile/config/BrowserSSOProfileConfiguration.java
@@ -43,6 +43,7 @@ import net.shibboleth.utilities.java.support.logic.FunctionSupport;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
@@ -83,6 +84,9 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
/** Lookup function to supply maximum delegation chain length. */
@Nonnull private Function<ProfileRequestContext,Long> maximumTokenDelegationChainLengthLookupStrategy;
+ /** Lookup function for requested AC operator. */
+ @Nonnull private Function<ProfileRequestContext,String> authnContextComparisonLookupStrategy;
+
/** Lookup function to supply default authentication methods. */
@Nonnull private Function<ProfileRequestContext,Collection<AuthnContextClassRefPrincipal>>
defaultAuthenticationContextsLookupStrategy;
@@ -119,6 +123,7 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
allowDelegationPredicate = Predicates.alwaysFalse();
authenticationFlowsLookupStrategy = FunctionSupport.constant(null);
postAuthenticationFlowsLookupStrategy = FunctionSupport.constant(null);
+ authnContextComparisonLookupStrategy = FunctionSupport.constant(null);
defaultAuthenticationContextsLookupStrategy = FunctionSupport.constant(null);
nameIDFormatPrecedenceLookupStrategy = FunctionSupport.constant(null);
}
@@ -357,6 +362,51 @@ public class BrowserSSOProfileConfiguration extends AbstractSAML2ArtifactAwarePr
maximumTokenDelegationChainLengthLookupStrategy =
Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
}
+
+ /**
+ * Get the comparison operator to use when issuing SAML requests containing requested context classes.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return comparison value or null
+ *
+ * @since 4.0.0
+ */
+ @Nullable public AuthnContextComparisonTypeEnumeration getAuthnContextComparison(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+
+ final String comparison = authnContextComparisonLookupStrategy.apply(profileRequestContext);
+ if (comparison != null) {
+ return AuthnContextComparisonTypeEnumeration.valueOf(comparison.toUpperCase());
+ }
+
+ return null;
+ }
+
+ /**
+ * Set the comparison operator to use when issuing SAML requests containing requested context classes.
+ *
+ * @param comparison comparison value or null
+ *
+ * @since 4.0.0
+ */
+ public void setAuthnContextComparison(@Nullable final AuthnContextComparisonTypeEnumeration comparison) {
+ authnContextComparisonLookupStrategy =
+ FunctionSupport.constant(comparison != null ? comparison.toString() : null);
+ }
+
+ /**
+ * Set a lookup strategy for the comparison operator to use when issuing SAML requests containing
+ * requested context classes.
+ *
+ * @param strategy lookup strategy
+ *
+ * @since 4.0.0
+ */
+ public void setAuthnContextComparisonLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ authnContextComparisonLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
/** {@inheritDoc} */
@Nonnull @NonnullElements @NotLive @Unmodifiable public List<Principal> getDefaultAuthenticationMethods(
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageDecoderFactory.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageDecoderFactory.java
new file mode 100644
index 0000000..0b184b9
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageDecoderFactory.java
@@ -0,0 +1,114 @@
+/*
+ * 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.saml.profile.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+
+import org.opensaml.messaging.decoder.MessageDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+
+/**
+ * A function that returns the correct {@link MessageDecoder} to use based on a simple map of
+ * strings to bean IDs.
+ */
+ at ThreadSafeAfterInit
+public class SpringAwareMessageDecoderFactory extends AbstractInitializableComponent
+ implements Function<String,MessageDecoder>, ApplicationContextAware {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SpringAwareMessageDecoderFactory.class);
+
+ /** Application context injected by surroundings. */
+ @Nullable private ApplicationContext applicationContext;
+
+ /** Map of strings to bean IDs. */
+ @Nonnull private Map<String,String> beanMappings;
+
+ /** Constructor. */
+ public SpringAwareMessageDecoderFactory() {
+ beanMappings = Collections.emptyMap();
+ }
+
+ /**
+ * Set mappings of strings to names of {@link MessageDecoder} beans.
+ *
+ * @param mappings string to bean ID mappings
+ */
+ public void setBeanMappings(@Nonnull final Map<String,String> mappings) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ Constraint.isNotNull(mappings, "Mappings cannot be null");
+
+ beanMappings = new HashMap<>(mappings.size());
+
+ for (final Map.Entry<String,String> entry : mappings.entrySet()) {
+ final String key = StringSupport.trimOrNull(entry.getKey());
+ final String value = StringSupport.trimOrNull(entry.getValue());
+ if (key != null && value != null) {
+ beanMappings.put(key, value);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void setApplicationContext(@Nullable final ApplicationContext context) {
+ applicationContext = context;
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public MessageDecoder apply(@Nullable final String input) {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+
+ final String beanID = beanMappings.get(StringSupport.trimOrNull(input));
+
+ if (applicationContext == null) {
+ log.warn("No Spring ApplicationContext set");
+ return null;
+ } else if (beanID == null) {
+ log.warn("No bean ID associated with input value {}", input);
+ return null;
+ }
+
+ log.debug("Looking up message decoder with bean ID: {}", beanID);
+
+ try {
+ return applicationContext.getBean(beanID, MessageDecoder.class);
+ } catch (final BeansException e) {
+ log.warn("Error instantiating message decoder from bean ID {}", beanID, e);
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageEncoderFactory.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageEncoderFactory.java
index 77bb0ff..b34e8f7 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageEncoderFactory.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/profile/impl/SpringAwareMessageEncoderFactory.java
@@ -23,6 +23,9 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.shibboleth.idp.saml.binding.BindingDescriptor;
+import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
import org.opensaml.messaging.encoder.MessageEncoder;
import org.opensaml.profile.context.ProfileRequestContext;
@@ -37,7 +40,8 @@ import org.springframework.context.ApplicationContextAware;
/**
* A function that returns the correct {@link MessageEncoder} to use based on an underlying {@link BindingDescriptor}.
*/
-public class SpringAwareMessageEncoderFactory
+ at ThreadSafeAfterInit
+public class SpringAwareMessageEncoderFactory extends AbstractInitializableComponent
implements Function<ProfileRequestContext,MessageEncoder>, ApplicationContextAware {
/** Class logger. */
@@ -53,6 +57,7 @@ public class SpringAwareMessageEncoderFactory
/** {@inheritDoc} */
@Nullable public MessageEncoder apply(@Nullable final ProfileRequestContext profileRequestContext) {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
if (applicationContext == null) {
log.warn("No Spring ApplicationContext set");
@@ -86,4 +91,4 @@ public class SpringAwareMessageEncoderFactory
return null;
}
-}
\ No newline at end of file
+}
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
similarity index 61%
copy from idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java
copy to idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
index bcd6666..b50a733 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/AddAuthnRequest.java
@@ -15,16 +15,20 @@
* limitations under the License.
*/
-package net.shibboleth.idp.saml.session.impl;
+package net.shibboleth.idp.saml.saml2.profile.impl;
import java.time.Instant;
+import java.util.List;
import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import net.shibboleth.idp.saml.session.SAML2SPSession;
-import net.shibboleth.idp.session.context.LogoutPropagationContext;
+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.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy;
@@ -32,69 +36,64 @@ import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifie
import org.opensaml.core.xml.XMLObjectBuilderFactory;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
-import org.opensaml.core.xml.io.MarshallingException;
-import org.opensaml.core.xml.io.UnmarshallingException;
-import org.opensaml.core.xml.util.XMLObjectSupport;
import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.AbstractProfileAction;
+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.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.common.SAMLVersion;
+import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
-import org.opensaml.saml.saml2.core.LogoutRequest;
-import org.opensaml.saml.saml2.core.NameID;
-import org.opensaml.saml.saml2.core.SessionIndex;
+import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Action that creates a {@link LogoutRequest} based on an {@link SAML2SPSession} in a
- * {@link LogoutPropagationContext} and sets it as the message returned by
+ * Action that creates an {@link AuthnRequest} and sets it as the message returned by
* {@link ProfileRequestContext#getOutboundMessageContext()}.
*
* <p>If an issuer value is returned via a lookup strategy, then it's set as the Issuer of the message.</p>
*
+ * <p>Various other values are derived from the active configuration. The outbound relay state is also
+ * set to the flow execution key.</p>
+ *
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_MSG_CTX}
* @event {@link EventIds#INVALID_PROFILE_CTX}
* @event {@link EventIds#MESSAGE_PROC_ERROR}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
*
* @post ProfileRequestContext.getOutboundMessageContext().getMessage() != null
*/
-public class AddLogoutRequest extends AbstractProfileAction {
+public class AddAuthnRequest extends AbstractAuthenticationAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(AddLogoutRequest.class);
+ @Nonnull private Logger log = LoggerFactory.getLogger(AddAuthnRequest.class);
/** Overwrite an existing message? */
private boolean overwriteExisting;
- /** Include SessionIndex in the request? */
- private boolean includeSessionIndex;
-
/** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
@Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+ /** Strategy used to obtain the relay state token to provide. */
+ @Nonnull private Function<ProfileRequestContext,String> relayStateLookupStrategy;
+
/** Strategy used to obtain the response issuer value. */
@Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
-
- /** Logout propagation context lookup strategy. */
- @Nonnull private Function<ProfileRequestContext,LogoutPropagationContext> logoutPropContextLookupStrategy;
/** The generator to use. */
@Nullable private IdentifierGenerationStrategy idGenerator;
-
- /** The {@link SAML2SPSession} to base the inbound context on. */
- @Nullable private SAML2SPSession saml2Session;
+
+ /** Applicable profile configuration. */
+ @Nullable private BrowserSSOProfileConfiguration profileConfiguration;
/** EntityID to populate into Issuer element. */
@Nullable private String issuerId;
/** Constructor. */
- public AddLogoutRequest() {
+ public AddAuthnRequest() {
// Default strategy is a 16-byte secure random source.
idGeneratorLookupStrategy = new Function<>() {
public IdentifierGenerationStrategy apply(final ProfileRequestContext input) {
@@ -102,8 +101,8 @@ public class AddLogoutRequest extends AbstractProfileAction {
}
};
- logoutPropContextLookupStrategy = new ChildContextLookup<>(LogoutPropagationContext.class);
- includeSessionIndex = true;
+ // Fool the parent class into looking above instead of below the PRC for the context.
+ setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
}
/**
@@ -118,17 +117,6 @@ public class AddLogoutRequest extends AbstractProfileAction {
}
/**
- * Set whether to include a SessionIndex in the request.
- *
- * @param flag flag to set
- */
- public void setIncludeSessionIndex(final boolean flag) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- includeSessionIndex = flag;
- }
-
- /**
* Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
*
* @param strategy lookup strategy
@@ -140,37 +128,47 @@ public class AddLogoutRequest extends AbstractProfileAction {
idGeneratorLookupStrategy =
Constraint.isNotNull(strategy, "IdentifierGenerationStrategy lookup strategy cannot be null");
}
-
+
/**
- * Set the strategy used to locate the issuer value to use.
+ * Set the strategy used to obtain the RelayState value to supply for flow restoration.
*
* @param strategy lookup strategy
*/
- public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ public void setRelayStateLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- issuerLookupStrategy = strategy;
+
+ relayStateLookupStrategy = Constraint.isNotNull(strategy, "RelayState lookup srategy cannot be null");
}
/**
- * Set the logout propagation context lookup strategy.
+ * Set the strategy used to locate the issuer value to use.
*
* @param strategy lookup strategy
*/
- public void setLogoutPropagationContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext,LogoutPropagationContext> strategy) {
+ public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- logoutPropContextLookupStrategy =
- Constraint.isNotNull(strategy, "LogoutPropagationContext lookup strategy cannot be null");
+ issuerLookupStrategy = strategy;
}
-// Checkstyle: CyclomaticComplexity OFF
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
- if (!super.doPreExecute(profileRequestContext)) {
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
+ if (rpCtx != null && rpCtx.getConfiguration() != null &&
+ rpCtx.getProfileConfig() instanceof BrowserSSOProfileConfiguration) {
+ profileConfiguration = (BrowserSSOProfileConfiguration) rpCtx.getProfileConfig();
+ }
+ if (profileConfiguration == null) {
+ log.error("{} BrowserSSOProfileConfiguration not found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
return false;
}
@@ -180,7 +178,7 @@ public class AddLogoutRequest extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
} else if (!overwriteExisting && outboundMessageCtx.getMessage() != null) {
- log.debug("{} Outbound message context already contains a response", getLogPrefix());
+ log.debug("{} Outbound message context already contains a message", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
@@ -196,54 +194,34 @@ public class AddLogoutRequest extends AbstractProfileAction {
issuerId = issuerLookupStrategy.apply(profileRequestContext);
}
- final LogoutPropagationContext logoutPropCtx = logoutPropContextLookupStrategy.apply(profileRequestContext);
- if (logoutPropCtx == null) {
- log.debug("{} No logout propagation context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- } else if (logoutPropCtx.getSession() == null || !(logoutPropCtx.getSession() instanceof SAML2SPSession)) {
- log.debug("{} Logout propgation context did not contain a SAML2SPSession", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
- saml2Session = (SAML2SPSession) logoutPropCtx.getSession();
- if (saml2Session.getId() == null) {
- log.debug("{} SAML2SPSession in logout propagation context did not contain a service ID", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
- return false;
- }
-
outboundMessageCtx.setMessage(null);
return true;
}
+// Checkstyle: CyclomaticComplexity ON
/** {@inheritDoc} */
@Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+ log.debug("{} Building AuthnRequest for upstream IdP ({})", getLogPrefix(),
+ authenticationContext.getAuthenticatingAuthority());
+
final XMLObjectBuilderFactory bf = XMLObjectProviderRegistrySupport.getBuilderFactory();
- final SAMLObjectBuilder<LogoutRequest> requestBuilder =
- (SAMLObjectBuilder<LogoutRequest>) bf.<LogoutRequest>getBuilderOrThrow(
- LogoutRequest.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<AuthnRequest> requestBuilder =
+ (SAMLObjectBuilder<AuthnRequest>) bf.<AuthnRequest>getBuilderOrThrow(
+ AuthnRequest.DEFAULT_ELEMENT_NAME);
+ final SAMLObjectBuilder<NameIDPolicy> nipBuilder =
+ (SAMLObjectBuilder<NameIDPolicy>) bf.<NameIDPolicy>getBuilderOrThrow(
+ NameIDPolicy.DEFAULT_ELEMENT_NAME);
- final LogoutRequest object = requestBuilder.buildObject();
+ final AuthnRequest object = requestBuilder.buildObject();
object.setID(idGenerator.generateIdentifier());
object.setIssueInstant(Instant.now());
object.setVersion(SAMLVersion.VERSION_20);
- try {
- final NameID nameId = XMLObjectSupport.cloneXMLObject(saml2Session.getNameID());
- object.setNameID(nameId);
- } catch (final MarshallingException|UnmarshallingException e) {
- log.error("{} Error cloning NameID for use in LogoutRequest for {}", getLogPrefix(),
- saml2Session.getId(), e);
- ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
- return;
- }
-
if (issuerId != null) {
log.debug("{} Setting Issuer to {}", getLogPrefix(), issuerId);
final SAMLObjectBuilder<Issuer> issuerBuilder =
@@ -255,17 +233,31 @@ public class AddLogoutRequest extends AbstractProfileAction {
log.debug("{} No issuer value available, leaving Issuer unset", getLogPrefix());
}
- if (includeSessionIndex) {
- final SAMLObjectBuilder<SessionIndex> indexBuilder =
- (SAMLObjectBuilder<SessionIndex>) bf.<SessionIndex>getBuilderOrThrow(
- SessionIndex.DEFAULT_ELEMENT_NAME);
- final SessionIndex index = indexBuilder.buildObject();
- index.setSessionIndex(saml2Session.getSessionIndex());
- object.getSessionIndexes().add(index);
+ // ForceAuthn may come from request or config.
+ if (authenticationContext.isForceAuthn() || profileConfiguration.isForceAuthn(profileRequestContext)) {
+ log.debug("{} Setting ForceAuthn for SAML AuthnRequest", getLogPrefix());
+ object.setForceAuthn(true);
+ }
+
+ // Only set passive based on request.
+ if (authenticationContext.isPassive()) {
+ log.debug("{} Setting IsPassive for SAML AuthnRequest", getLogPrefix());
+ object.setIsPassive(true);
+ }
+
+ final NameIDPolicy nip = nipBuilder.buildObject();
+ nip.setAllowCreate(true);
+
+ // TODO: use metadata for NameID Formats too?
+ final List<String> formats = profileConfiguration.getNameIDFormatPrecedence(profileRequestContext);
+ if (!formats.isEmpty()) {
+ log.debug("{} Setting NameIDPolicy Format to '{}' for SAML AuthnRequest", getLogPrefix(), formats.get(0));
+ nip.setFormat(formats.get(0));
}
+ object.setNameIDPolicy(nip);
+
profileRequestContext.getOutboundMessageContext().setMessage(object);
}
-// Checkstyle: CyclomaticComplexity ON
}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnContext.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnContext.java
new file mode 100644
index 0000000..9ee5dc1
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnContext.java
@@ -0,0 +1,101 @@
+/*
+ * 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.saml.saml2.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.decoder.MessageDecoder;
+import org.opensaml.messaging.handler.MessageHandler;
+import org.opensaml.profile.action.ProfileAction;
+
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Manages state for the webflow -> external -> webflow transitions
+ * during proxied SAML authentication.
+ *
+ * @since 4.0.0
+ */
+public class SAMLAuthnContext 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 ProfileAction encodeMessageAction;
+
+ /** The function to use to obtain a decoder. */
+ @Nonnull private Function<String,MessageDecoder> decoderFactory;
+
+ /**
+ * Constructor.
+ *
+ * @param action message-encoding profile action
+ * @param factory factory function to obtain decoders
+ */
+ public SAMLAuthnContext(@Nonnull final ProfileAction action,
+ @Nonnull final Function<String,MessageDecoder> factory) {
+ encodeMessageAction = Constraint.isNotNull(action, "Profile action cannot be null");
+ decoderFactory = Constraint.isNotNull(factory, "MessageDecoder factory cannot be null");
+ }
+
+ /**
+ * Get the message-encoding profile action.
+ *
+ * @return profile action
+ */
+ @Nonnull public ProfileAction getEncodeMessageAction() {
+ return encodeMessageAction;
+ }
+
+ /**
+ * Get the factory function to obtain message decoders.
+ *
+ * @return factory function
+ */
+ @Nonnull public Function<String,MessageDecoder> getMessageDecoderFactory() {
+ return decoderFactory;
+ }
+
+ /**
+ * 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 SAMLAuthnContext setOutboundMessageHandler(@Nullable final MessageHandler handler) {
+ outboundMessageHandler = handler;
+
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnController.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnController.java
new file mode 100644
index 0000000..99326bd
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/SAMLAuthnController.java
@@ -0,0 +1,239 @@
+/*
+ * 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.saml.saml2.profile.impl;
+
+import java.io.IOException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.shibboleth.idp.authn.ExternalAuthentication;
+import net.shibboleth.idp.authn.ExternalAuthenticationException;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.decoder.MessageDecoder;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+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.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * MVC controller that handles outbound and inbound message I/O for
+ * proxied SAML authentication.
+ *
+ * <p>Outbound messaging is necessary to ensure webflow hygiene with respect to
+ * flow state, and inbound messaging is necessary to ensure a fixed URL for
+ * SAML endpoint management.</p>
+ *
+ * @since 4.0.0
+ */
+ at Controller
+ at RequestMapping("%{idp.authn.saml.externalAuthnPath:/Authn/SAML2}")
+public class SAMLAuthnController extends AbstractInitializableComponent {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(SAMLAuthnController.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,SAMLAuthnContext> samlContextLookupStrategy;
+
+ /** Constructor. */
+ public SAMLAuthnController() {
+ // PRC -> AC -> nested PRC
+ profileRequestContextLookupStrategy = new ChildContextLookup<>(ProfileRequestContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+
+ // PRC -> AC -> SAMLAuthnContext
+ samlContextLookupStrategy = new ChildContextLookup<>(SAMLAuthnContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class));
+ }
+
+ /**
+ * Set the lookup strategy used to locate the nested {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileRequestContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,ProfileRequestContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ profileRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+ "ProfileRequestContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy used to locate the {@link SAMLAuthnContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSAMLAuthnContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,SAMLAuthnContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ samlContextLookupStrategy = Constraint.isNotNull(strategy, "SAMLAuthnContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Outbound initiation of the process, triggered with a fixed addition to the path.
+ *
+ * @param httpRequest servlet request
+ * @param httpResponse servlet response
+ * @param binding a key for the eventual inbound binding
+ *
+ * @throws ExternalAuthenticationException if an error occurs
+ * @throws IOException if an I/O error occurs
+ */
+ @GetMapping("/{binding}/SSO/start")
+ @Nullable public void startSAML(@Nonnull final HttpServletRequest httpRequest,
+ @Nonnull final HttpServletResponse httpResponse, @PathVariable @Nonnull @NotEmpty final String binding)
+ throws ExternalAuthenticationException, IOException {
+
+ final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
+ final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+
+ final SAMLAuthnContext samlContext = samlContextLookupStrategy.apply(prc);
+ if (samlContext == null) {
+ log.error("SAMLAuthnContext 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;
+ }
+
+ // Fill in the AuthnRequest's ACS URL and set RelayState to the EA key.
+ if (nestedPRC.getOutboundMessageContext() != null &&
+ nestedPRC.getOutboundMessageContext().getMessage() instanceof AuthnRequest) {
+ SAMLBindingSupport.setRelayState(nestedPRC.getOutboundMessageContext(), key);
+ final StringBuffer url = httpRequest.getRequestURL();
+ ((AuthnRequest) nestedPRC.getOutboundMessageContext().getMessage()).setAssertionConsumerServiceURL(
+ url.substring(0, url.lastIndexOf("/start")));
+ } else {
+ log.error("Outbound AuthnContext message not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_MESSAGE);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+
+ try {
+ if (samlContext.getOutboundMessageHandler() != null) {
+ samlContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext());
+ }
+
+ samlContext.getEncodeMessageAction().execute(nestedPRC);
+ 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;
+ }
+ } 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);
+ }
+ }
+
+ /**
+ * Inbound completion of the process, triggered by default for any methods.
+ *
+ * @param httpRequest servlet request
+ * @param httpResponse servlet response
+ * @param binding a key for the inbound binding
+ *
+ * @throws ExternalAuthenticationException if an error occurs
+ * @throws IOException if an I/O error occurs
+ */
+ @RequestMapping("/{binding}/SSO")
+ @Nullable public void finishSAML(@Nonnull final HttpServletRequest httpRequest,
+ @Nonnull final HttpServletResponse httpResponse, @PathVariable @Nonnull @NotEmpty final String binding)
+ throws ExternalAuthenticationException, IOException {
+
+ final String key = httpRequest.getParameter("RelayState");
+ if (key == null) {
+ throw new ExternalAuthenticationException("No RelayState parameter, unable to resume flow execution");
+ }
+
+ final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+ final SAMLAuthnContext samlContext = samlContextLookupStrategy.apply(prc);
+ if (samlContext == null) {
+ log.error("SAMLAuthnContext 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;
+ }
+
+ try {
+ final MessageDecoder decoder = samlContext.getMessageDecoderFactory().apply(binding);
+ if (decoder == null) {
+ throw new MessageDecodingException("Unable to obtain MessageDecoder for binding key: " + binding);
+ }
+ try {
+ decoder.initialize();
+ decoder.decode();
+ nestedPRC.setInboundMessageContext(decoder.getMessageContext());
+ } finally {
+ decoder.destroy();
+ }
+ } catch (final MessageDecodingException | ComponentInitializationException e) {
+ log.error("Unable to decode SAML response", e);
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.UNABLE_TO_DECODE);
+ }
+
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java
new file mode 100644
index 0000000..a89ca72
--- /dev/null
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/saml2/profile/impl/ValidateSAMLAuthentication.java
@@ -0,0 +1,94 @@
+/*
+ * 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.saml.saml2.profile.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.saml2.core.Response;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that checks for an {@link ExternalAuthenticationContext} for a signaled event via the
+ * {@link ExternalAuthenticationContext#getAuthnError()} method, and otherwise enforces the presence
+ * of an inbound SAML Response to process.
+ *
+ * <p>This is a bridge from the external portion of the SAML proxy implementation to transition
+ * back into the flow and pick up any signaled errors if necessary.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@Link EventIds#MESSAGE_PROC_ERROR}
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @event various
+ */
+public class ValidateSAMLAuthentication extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateSAMLAuthentication.class);
+
+ /** Context containing the result to examine. */
+ @Nullable private ExternalAuthenticationContext extContext;
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ extContext = authenticationContext.getSubcontext(ExternalAuthenticationContext.class);
+ if (extContext == null) {
+ log.debug("{} No ExternalAuthenticationContext available within authentication context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (extContext.getAuthnError() != null) {
+ log.info("{} SAML authentication attempt signaled an error: {}", getLogPrefix(),
+ extContext.getAuthnError());
+ ActionSupport.buildEvent(profileRequestContext, extContext.getAuthnError());
+ } else if (profileRequestContext.getInboundMessageContext() == null) {
+ log.info("{} No inbound SAML Response found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ } else if (!(profileRequestContext.getInboundMessageContext().getMessage() instanceof Response)) {
+ log.info("{} Inbound message was not a SAML Response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java
index bcd6666..a263af5 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/AddLogoutRequest.java
@@ -180,7 +180,7 @@ public class AddLogoutRequest extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
} else if (!overwriteExisting && outboundMessageCtx.getMessage() != null) {
- log.debug("{} Outbound message context already contains a response", getLogPrefix());
+ log.debug("{} Outbound message context already contains a message", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
diff --git a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContext.java b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContext.java
index 9887f36..17febf8 100644
--- a/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContext.java
+++ b/idp-saml-impl/src/main/java/net/shibboleth/idp/saml/session/impl/PrepareInboundMessageContext.java
@@ -39,15 +39,15 @@ import org.slf4j.LoggerFactory;
/**
* Action that adds an inbound {@link MessageContext} and a {@link SAMLPeerEntityContext} to the
- * {@link ProfileRequestContext} based on the identity of the relying party bound to a {@link SAML2SPSession}
- * found in a {@link LogoutPropagationContext}.
+ * {@link ProfileRequestContext} based on the identity of a relying party, by default from a
+ * {@link SAML2SPSession} found in a {@link LogoutPropagationContext}.
*
- * <p>This action mocks up a minimal amount of machinery on the inbound message side to drive a
+ * <p>This action primarily mocks up a minimal amount of machinery on the inbound message side to drive a
* SAML 2 Logout Propagation flow, which needs to issue a logout request message for the {@link SAML2SPSession}
* it's given.</p>
*
- * <p>It's named generically so that if we need to expand it to support something beyond SAML 2 (some kind of
- * hack for SAML 1?) we can do that.
+ * <p>It has some generic capability to allow it to be used for some other outbound messaging cases, such as
+ * SAML 2 SSO proxying.</p>
*
* @event {@link EventIds#PROCEED_EVENT_ID}
* @event {@link EventIds#INVALID_PROFILE_CTX}
@@ -56,12 +56,15 @@ public class PrepareInboundMessageContext extends AbstractProfileAction {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(PrepareInboundMessageContext.class);
-
+
/** Logout propagation context lookup strategy. */
@Nonnull private Function<ProfileRequestContext,LogoutPropagationContext> logoutPropContextLookupStrategy;
- /** The {@link SAML2SPSession} to base the inbound context on. */
- @Nullable private SAML2SPSession saml2Session;
+ /** Optional circumvention of usual method to identify the relying party name. */
+ @Nullable private Function<ProfileRequestContext,String> relyingPartyLookupStrategy;
+
+ /** The relying party name to base the inbound context on. */
+ @Nullable private String relyingPartyId;
/** Constructor. */
public PrepareInboundMessageContext() {
@@ -80,6 +83,20 @@ public class PrepareInboundMessageContext extends AbstractProfileAction {
logoutPropContextLookupStrategy =
Constraint.isNotNull(strategy, "LogoutPropagationContext lookup strategy cannot be null");
}
+
+ /**
+ * Set an optional lookup strategy to identify the relying party name, as a substitute for the session/logout
+ * assumptions made by the action otherwise.
+ *
+ * @param strategy lookup strategy
+ *
+ * @since 4.0.0
+ */
+ public void setRelyingPartyLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ relyingPartyLookupStrategy = strategy;
+ }
/** {@inheritDoc} */
@Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -88,6 +105,13 @@ public class PrepareInboundMessageContext extends AbstractProfileAction {
return false;
}
+ if (relyingPartyLookupStrategy != null) {
+ relyingPartyId = relyingPartyLookupStrategy.apply(profileRequestContext);
+ if (relyingPartyId != null) {
+ return true;
+ }
+ }
+
final LogoutPropagationContext logoutPropCtx = logoutPropContextLookupStrategy.apply(profileRequestContext);
if (logoutPropCtx == null) {
log.debug("{} No logout propagation context", getLogPrefix());
@@ -99,8 +123,7 @@ public class PrepareInboundMessageContext extends AbstractProfileAction {
return false;
}
- saml2Session = (SAML2SPSession) logoutPropCtx.getSession();
-
+ relyingPartyId = ((SAML2SPSession) logoutPropCtx.getSession()).getId();
return true;
}
@@ -111,9 +134,9 @@ public class PrepareInboundMessageContext extends AbstractProfileAction {
profileRequestContext.setInboundMessageContext(msgCtx);
final SAMLPeerEntityContext peerContext = msgCtx.getSubcontext(SAMLPeerEntityContext.class, true);
- peerContext.setEntityId(saml2Session.getId());
+ peerContext.setEntityId(relyingPartyId);
- log.debug("{} Initialized inbound message context for logout of {}", getLogPrefix(), saml2Session.getId());
+ log.debug("{} Initialized inbound context for message to {}", getLogPrefix(), relyingPartyId);
}
}
\ 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