[java-idp-plugin-oidc-rp] branch main updated: Fix contexts, add auth controller callback, add auth_code token exchange
Phil Smart
philip.smart at jisc.ac.uk
Mon Jan 31 17:54:12 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=106242b1ea6f2875f0177c96d438c860850bc7bb
The following commit(s) were added to refs/heads/main by this push:
new 106242b Fix contexts, add auth controller callback, add auth_code token exchange
106242b is described below
commit 106242b1ea6f2875f0177c96d438c860850bc7bb
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jan 31 17:54:05 2022 +0000
Fix contexts, add auth controller callback, add auth_code token exchange
- Cleanup the inbound and outbound context trees
- Add response_mode support and lookup
- Add/complete callback to authz controller
- Start flow from authz callback
- Validate authentication response
- Add token request support for auth_code grant.
- Add supporting encoders/decoders for token request/response
- Improve flow
- Cleanup old classes
- Fix some checkstyle/javadoc
---
.../plugin/authn/oidc/rp/OIDCProxyException.java | 54 ++
.../authn/oidc/rp/context/OIDCAuthnContext.java | 37 +-
...dataContext.java => OIDCPeerEntityContext.java} | 42 +-
.../rp/context/ResponseTypeAndModeContext.java | 57 ++
...adataContext.java => TokenResponseContext.java} | 45 +-
.../context/OIDCClientMetadataContext.java} | 7 +-
.../context/OIDCProviderMetadataContext.java | 1 +
idp-oidc-rp-impl/pom.xml | 11 +
.../idp/plugin/authn/oidc/rp/OIDCRPModule.java | 17 +
.../idp/plugin/authn/oidc/rp/OIDCRPPlugin.java | 17 +
.../decoding/impl/DefaultTokenResponseDecoder.java | 101 ++++
.../authn/oidc/rp/decoding/impl/package-info.java | 21 +
.../impl/AbstractRequestEncoderFunction.java | 199 +++++++
.../encoding/impl/DefaultTokenRequestEncoder.java | 132 +++++
.../authn/oidc/rp/encoding/impl/package-info.java | 21 +
.../impl/AbstractHttpOIDCAuthenticationAction.java | 111 ++++
.../AbstractOIDCAuthenticationResponseAction.java | 204 +++++++
.../plugin/authn/oidc/rp/impl/AddAuthzRequest.java | 103 +++-
.../oidc/rp/impl/AuthorizationController.java | 115 ++--
.../rp/impl/DefaultResponseModeLookupFunction.java | 62 +++
.../rp/impl/DefaultResponseTypeLookupFunction.java | 73 +++
.../authn/oidc/rp/impl/ExchangeCodeForToken.java | 157 ++++++
.../oidc/rp/impl/ExtractIDTokenFromResponse.java | 108 ++++
.../authn/oidc/rp/impl/GetOIDCTokenResponse.java | 137 -----
...OutboundAuthorizationRequestMessageContext.java | 68 ++-
.../rp/impl/InitializeRelyingPartyContext.java | 57 +-
.../rp/impl/OIDCAuthenticationTimeRequested.java | 17 +
.../OIDCContextAudienceClaimLookupStrategy.java | 3 +-
.../authn/oidc/rp/impl/OIDCProxySupport.java | 68 ++-
.../impl/PopulateResponseTypeAndModeContext.java | 119 +++++
.../rp/impl/PrepareOIDCInboundMessageContext.java | 105 ++++
.../authn/oidc/rp/impl/SetOIDCInformation.java | 584 ---------------------
.../impl/TokenResponseIDTokenLookupStrategy.java | 54 ++
.../impl/ValidateAuthenticationResponseResult.java | 88 ++++
.../ValidateExternalAuthenticationContext.java | 84 +++
.../authn/oidc/rp/impl/ValidateIDTokenACR.java | 103 ----
.../rp/impl/ValidateIDTokenAuthorizedParty.java | 89 ----
.../impl/ValidateOIDCAuthenticationResponse.java | 123 -----
.../authn/oidc/rp/impl/ValidateResponseState.java | 81 +++
.../DefaultClientIDForIssuerLookupFunction.java | 59 +++
.../impl/DefaultIssuerIDLookupFunction.java | 11 +-
...r.java => OIDCClientMetadataLookupHandler.java} | 14 +-
.../impl/OIDCProviderMetadataLookupHandler.java | 50 +-
.../oidc-relying-party-authn-beans.xml | 128 ++++-
.../oidc-relying-party-authn-flow.xml | 79 ++-
.../impl/DefaultTokenResponseDecoderTest.java | 109 ++++
.../impl/DefaultTokenRequestEncoderTest.java | 61 +++
.../authn/oidc/rp/impl/AbstractOIDCTest.java | 177 +++++++
.../oidc/rp/impl/AuthorizationControllerTest.java | 42 +-
.../oidc/rp/impl/ExchangeCodeForTokenTest.java | 176 +++++++
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 203 ++++++-
51 files changed, 3345 insertions(+), 1239 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java
new file mode 100644
index 0000000..c79918a
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCProxyException.java
@@ -0,0 +1,54 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * An exception to signal a general Duo error.
+ */
+ at ThreadSafe
+public class OIDCProxyException extends Exception{
+
+ /** Generated serial UID. */
+ private static final long serialVersionUID = 1360790617215053967L;
+
+ /** Constructor. */
+ public OIDCProxyException() {
+ super();
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message
+ * @param cause exception to be wrapped by this one
+ */
+ public OIDCProxyException(final String message, final Throwable cause) {
+ super(message, cause);
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message exception message
+ */
+ public OIDCProxyException(final String message) {
+ super(message);
+
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param cause exception to be wrapped by this one
+ */
+ public OIDCProxyException(final Throwable cause) {
+ super(cause);
+
+ }
+
+
+
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
index 76cb049..bfb945f 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCAuthnContext.java
@@ -1,9 +1,29 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+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;
@@ -20,13 +40,19 @@ public class OIDCAuthnContext extends BaseContext {
/** Profile action to execute to produce outbound message response. */
@Nonnull private final 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 the message descoder factory
*/
- public OIDCAuthnContext(@Nonnull final ProfileAction action) {
+ public OIDCAuthnContext(@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");
}
/**
@@ -58,5 +84,14 @@ public class OIDCAuthnContext extends BaseContext {
outboundMessageHandler = handler;
return this;
}
+
+ /**
+ * Get the factory function to obtain message decoders.
+ *
+ * @return factory function
+ */
+ @Nonnull public Function<String,MessageDecoder> getMessageDecoderFactory() {
+ return decoderFactory;
+ }
}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
similarity index 50%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
index 6bafcbb..86dcc70 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCPeerEntityContext.java
@@ -21,39 +21,37 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
/**
- * Subcontext carrying information on metadata of the relying party. This
- * context appears as a subcontext of the
- * {@link org.opensaml.messaging.context.MessageContext} that carries the actual
- * OIDC request message, in such cases the metadata carried herein applies to
- * the issuer of that message.
+ * Lightweight subcontext that carries information about a OIDC peer entity.
*
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
+ * <p>
+ * This context will often contain subcontexts, whose data is construed to be scoped to that peer entity.
+ * </p>
*/
-public class OIDCMetadataContext extends BaseContext {
-
- /** The client information. */
- @Nullable private OIDCClientInformation clientInformation;
+public final class OIDCPeerEntityContext extends BaseContext {
+
+ /** The identifier of the OIDC peer entity e.g. issuerId or ClientId. */
+ @Nullable @NotEmpty private String identifer;
/**
- * Set the client information.
+ * Gets the identifier of the OIDC entity.
*
- * @return The client information.
+ * @return identifier of the OIDC entity, may be null
*/
- @Nullable
- public OIDCClientInformation getClientInformation() {
- return clientInformation;
+ @Nullable @NotEmpty public String getIdentifier() {
+ return identifer;
}
/**
- * Set the client information.
+ * Sets the identifier of the OIDC entity e.g. issuerId or ClientId.
*
- * @param information The client information.
+ * @param id the new identifier
*/
- public void setClientInformation(@Nullable final OIDCClientInformation information) {
- clientInformation = information;
+ public void setIdentifier(@Nullable final String id) {
+ identifer = StringSupport.trimOrNull(id);
}
-}
\ No newline at end of file
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
new file mode 100644
index 0000000..b7113a8
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/ResponseTypeAndModeContext.java
@@ -0,0 +1,57 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+/**
+ * Context hold the response_type and response_mode to be used with the ongoing authentication request.
+ */
+public class ResponseTypeAndModeContext extends BaseContext {
+
+ /** The chosen response type.*/
+ @Nullable private ResponseType responseType;
+
+ /** The chosen response mode.*/
+ @Nullable private ResponseMode responseMode;
+
+ /**
+ * Set the response type to be used with the ongoing authentication request.
+ *
+ * @param type the response type.
+ */
+ public void setResponseType(@Nullable final ResponseType type) {
+ responseType = type;
+ }
+
+ /**
+ * Get the response_type to use with this authentication request.
+ *
+ * @return the response_type.
+ */
+ @Nullable public ResponseType getResponseType() {
+ return responseType;
+ }
+
+ /**
+ * Set the response mode to be used with the ongoing authentication request.
+ *
+ * @param mode the response mode.
+ */
+ public void setResponseMode(@Nullable final ResponseMode mode) {
+ responseMode = mode;
+ }
+
+ /**
+ * Get the response_mode to use with this authentication request.
+ *
+ * @return the response_mode.
+ */
+ @Nullable public ResponseMode getResponseMode() {
+ return responseMode;
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
similarity index 50%
copy from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
copy to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
index 6bafcbb..56a78d8 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/TokenResponseContext.java
@@ -17,43 +17,34 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+import java.util.Map;
+
import javax.annotation.Nullable;
import org.opensaml.messaging.context.BaseContext;
-import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-
-/**
- * Subcontext carrying information on metadata of the relying party. This
- * context appears as a subcontext of the
- * {@link org.opensaml.messaging.context.MessageContext} that carries the actual
- * OIDC request message, in such cases the metadata carried herein applies to
- * the issuer of that message.
- *
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
- */
-public class OIDCMetadataContext extends BaseContext {
-
- /** The client information. */
- @Nullable private OIDCClientInformation clientInformation;
+/** A context to hold an OIDC token request response.*/
+public class TokenResponseContext extends BaseContext {
+
+ /** The raw token response as a map.*/
+ @Nullable private Map<String, Object> rawTokenResponse;
/**
- * Set the client information.
+ * Set the raw token response as a map.
*
- * @return The client information.
+ * @param rawResponse the raw response.
*/
- @Nullable
- public OIDCClientInformation getClientInformation() {
- return clientInformation;
+ public void setRawTokenResponse(@Nullable final Map<String, Object> rawResponse) {
+ rawTokenResponse = rawResponse;
}
-
+
/**
- * Set the client information.
+ * Get the raw token response map.
*
- * @param information The client information.
+ * @return the raw response map.
*/
- public void setClientInformation(@Nullable final OIDCClientInformation information) {
- clientInformation = information;
+ @Nullable public Map<String, Object> getRawTokenResponse() {
+ return rawTokenResponse;
}
-}
\ No newline at end of file
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
similarity index 89%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
index 6bafcbb..f0b9f5b 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OIDCMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCClientMetadataContext.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+package net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context;
import javax.annotation.Nullable;
@@ -29,11 +29,8 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
* {@link org.opensaml.messaging.context.MessageContext} that carries the actual
* OIDC request message, in such cases the metadata carried herein applies to
* the issuer of that message.
- *
- * This context is just a placeholder for the final solution. At first phase we
- * use only redirect uris.
*/
-public class OIDCMetadataContext extends BaseContext {
+public class OIDCClientMetadataContext extends BaseContext {
/** The client information. */
@Nullable private OIDCClientInformation clientInformation;
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
index 118c312..849edac 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/messaging/context/OIDCProviderMetadataContext.java
@@ -29,6 +29,7 @@ public class OIDCProviderMetadataContext extends BaseContext {
*
* @param metadata the provider configuration information.
*/
+ //TODO not sure this is needed in constructor, make inline with other contexts.
public OIDCProviderMetadataContext(@Nonnull final OIDCProviderMetadata metadata) {
providerInformation = Constraint.isNotNull(metadata, "OIDC Provider Metadata can not be null");
}
diff --git a/idp-oidc-rp-impl/pom.xml b/idp-oidc-rp-impl/pom.xml
index e4ecd8e..a209dfd 100644
--- a/idp-oidc-rp-impl/pom.xml
+++ b/idp-oidc-rp-impl/pom.xml
@@ -128,6 +128,17 @@
<artifactId>opensaml-messaging-impl</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>${idp.groupId}</groupId>
+ <artifactId>idp-profile-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.shibboleth.idp</groupId>
+ <artifactId>idp-profile-api</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
</dependencies>
<build>
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
index 90397e7..b8c75dc 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPModule.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp;
import java.io.IOException;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
index e24c0dc..a5dd33f 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/OIDCRPPlugin.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp;
import java.io.IOException;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.java
new file mode 100644
index 0000000..4950a60
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoder.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.plugin.authn.oidc.rp.decoding.impl;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.util.IOUtils;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+
+/** Default token response decoder, which converts a succesful HTTP response into an Map.*/
+public class DefaultTokenResponseDecoder extends AbstractInitializableComponent
+ implements Function<HttpResponse, Map<String, Object>> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultTokenResponseDecoder.class);
+
+ /** JSON object mapper. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("objectMapper cannot be null");
+ }
+ }
+
+ /**
+ * Set the JSON Object Mapper to use.
+ *
+ * @param mapper the object mapper.
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
+ }
+
+ //TODO we should handle the error response better? just return it and make the decision later?
+ @Override
+ @Nullable public Map<String, Object> apply(@Nonnull final HttpResponse httpResponse) {
+
+ try {
+ final int httpStatusCode = httpResponse.getStatusLine().getStatusCode();
+ if (httpStatusCode != HttpStatus.SC_OK) {
+ //dump the body for logging - if one exists
+ if (httpResponse.getEntity() != null && httpResponse.getEntity().getContent() != null) {
+ final String errorContent = IOUtils.readInputStreamToString(httpResponse.getEntity().getContent());
+ log.error("Token endpoint returned a Non-ok message of '{}'",errorContent);
+ }
+ log.warn("Non-ok status code ({}) returned from token endpoint: {}", httpStatusCode,
+ httpResponse.getStatusLine().getReasonPhrase());
+ return null;
+ } else if (httpResponse.getEntity() == null || httpResponse.getEntity().getContent() == null) {
+ log.warn("HTTP response does not contain a message entity, nothing to decode");
+ return null;
+ }
+
+ return objectMapper.readValue(httpResponse.getEntity().getContent(),
+ new TypeReference<Map<String, Object>>() {});
+ } catch (final Exception e) {
+ log.warn("Unable to decode OIDC Token Request Response", e);
+ }
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java
new file mode 100644
index 0000000..7e769f9
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This package has decoders for various OIDC/OAuth2 responses.
+ */
+package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
new file mode 100644
index 0000000..bf4baa5
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+//TODO is the same as the AbstractOIDCAuthenticationAction.
+/** Abstract request encoder function that pulls out various contexts and request/reponse messages.*/
+public abstract class AbstractRequestEncoderFunction extends AbstractInitializableComponent
+ implements Function<ProfileRequestContext, HttpUriRequest> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(AbstractRequestEncoderFunction.class);
+
+ /** Lookup strategy to locate the OP metadata to use.*/
+ @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+
+ /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+ @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+
+ /** OIDC authentication response from downstream OP. */
+ @Nullable private AuthenticationSuccessResponse authnResponse;
+
+ /** OIDC authentication request built by the IdP. */
+ @Nullable private OIDCAuthenticationRequest authnRequest;
+
+ /** OIDC client Metadata context. */
+ @Nullable private OIDCClientMetadataContext clientMetadataContext;
+
+ /** OIDC Metadata context. */
+ @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+
+ /** Constructor.*/
+ protected AbstractRequestEncoderFunction() {
+ providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+ new OutboundMessageContextLookup());
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID providers metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID client metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setClientMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Returns the authentication response from the downstream OP.
+ *
+ * @return the authentication response.
+ */
+ @Nullable public AuthenticationSuccessResponse getAuthenticationResponse() {
+ return authnResponse;
+ }
+
+ /**
+ * Returns the authentication request build by this IdP.
+ *
+ * @return the authentication request.
+ */
+ @Nullable public OIDCAuthenticationRequest getAuthenticationRequest() {
+ return authnRequest;
+ }
+
+ /**
+ * Returns the client metadata context.
+ *
+ * @return The client metadata context.
+ */
+ @Nullable public OIDCClientMetadataContext getClientMetadataContext() {
+ return clientMetadataContext;
+ }
+
+ /**
+ * Returns the OIDC provider metadata context.
+ *
+ * @return The provider metadata context.
+ */
+ @Nullable public OIDCProviderMetadataContext getProviderMetadataContext() {
+ return providerMetadataContext;
+ }
+
+ //TODO these come from the outbound message context, so are not general to other actions
+
+ @Override
+ public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+
+ final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+ if (inboundMessageCtx == null) {
+ log.error("No inbound message context");
+ return null;
+ }
+ if (inboundMessageCtx.getMessage() == null) {
+ log.error("No inbound message");
+ return null;
+ }
+
+ if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+ log.error("No inbound authentication success response");
+ return null;
+ }
+ authnResponse = (AuthenticationSuccessResponse)inboundMessageCtx.getMessage();
+
+ final MessageContext outboundMsgContext = profileRequestContext.getOutboundMessageContext();
+ if (outboundMsgContext == null) {
+ log.error("Outbound message context was null");
+ return null;
+ }
+ if (!(outboundMsgContext.getMessage() instanceof OIDCAuthenticationRequest)) {
+ log.error("Outbound message was not an authentication request");
+ return null;
+ }
+ authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
+
+ clientMetadataContext = clientMetadataLookupStrategy.apply(profileRequestContext);
+ if (clientMetadataContext == null) {
+ log.error("No client metadata found for relying party");
+ return null;
+ }
+
+ providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+ if (providerMetadataContext == null) {
+ log.error("No provider metadata found for peer");
+ return null;
+ }
+
+ return doApply(profileRequestContext);
+
+ }
+
+ /**
+ * Encode a HttpUriRequest from the given context. Implementations should override this mehtod.
+ *
+ * @param profileRequestContext the profile request context.
+ *
+ * @return the request to execute.
+ */
+ protected abstract HttpUriRequest doApply(ProfileRequestContext profileRequestContext);
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
new file mode 100644
index 0000000..797306f
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
+
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.util.Base64;
+import com.nimbusds.jose.util.StandardCharset;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** A token request encoder that builds an OAuth2.0 Access Token Request for an authorization grant request. */
+//TODO either only applies to authorization_code grant, or can handle more. If specific, must gurantee that.
+public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
+
+ /** The HTTPS scheme.*/
+ @Nonnull @NotEmpty private static final String HTTPS = "https";
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(DefaultTokenRequestEncoder.class);
+
+ @Override
+ @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ try {
+ // Mandate HTTPS, so construct the URL from that.
+ final URI uri = new URIBuilder().setScheme(HTTPS)
+ .setHost(getProviderMetadataContext()
+ .getProviderInformation()
+ .getTokenEndpointURI().getHost())
+ .setPath(getProviderMetadataContext()
+ .getProviderInformation()
+ .getTokenEndpointURI().getPath())
+ .build();
+
+ // Add headers and create request.
+ final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+
+ // Add mandatory fields
+ addRequiredFields(rb);
+ // Add fields that are required if certain conditions are met.
+ addRequiredOnConditionFields(rb);
+ // Build client_secret_basic authentication. TODO support others.
+ buildSecretBasicAuthentication(rb);
+
+ final HttpUriRequest request = rb.build();
+ log.debug("Token URL '{}'",request);
+ return request;
+
+ } catch (final Exception e) {
+ log.warn("Unable to encode token request", e);
+ }
+ return null;
+ }
+
+ /**
+ * Add all required fields for the given grant_type to the request.
+ *
+ * @param rb the request builder.
+ */
+ private void addRequiredFields(@Nonnull final RequestBuilder rb) {
+
+ rb.addParameter("grant_type", "authorization_code")
+ .addParameter("code",getAuthenticationResponse().getAuthorizationCode().getValue());
+ }
+
+
+ /**
+ * Add all fields to the request that are required if a given condition is met.
+ *
+ * @param rb the request builder.
+ */
+ private void addRequiredOnConditionFields(@Nonnull final RequestBuilder rb) {
+
+ // Check redirect_uri was sent in authz request and add if so
+ if (getAuthenticationRequest().getRedirectURI() != null) {
+ rb.addParameter("redirect_uri", getAuthenticationRequest().getRedirectURI().toString());
+ }
+ }
+
+ /**
+ * Build the client_secret_basic authentication using pre-emptive HTTP Basic auth.
+ *
+ * @param rb the request builder to use.
+ */
+ //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this.
+ private void buildSecretBasicAuthentication(final RequestBuilder rb) {
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append(URLEncoder.encode(
+ getClientMetadataContext().getClientInformation().getID().getValue(), StandardCharsets.UTF_8));
+ sb.append(':');
+ sb.append(URLEncoder.encode(
+ getClientMetadataContext().getClientInformation().getSecret().getValue(), StandardCharsets.UTF_8));
+
+ final String authHeader = "Basic " + Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
+ rb.addHeader("Authorization", authHeader);
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java
new file mode 100644
index 0000000..f63a3fc
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This package has encoders for various OIDC/OAuth2 requests.
+ */
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
new file mode 100644
index 0000000..9dad4af
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractHttpOIDCAuthenticationAction.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.opensaml.security.httpclient.HttpClientSecurityParameters;
+import org.opensaml.security.httpclient.HttpClientSecuritySupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An abstract class for actions that want to make synchronous HTTP requests.
+ */
+public class AbstractHttpOIDCAuthenticationAction extends AbstractOIDCAuthenticationAction {
+
+ /** Class logger.*/
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractHttpOIDCAuthenticationAction.class);
+
+ /** HttpClient for contacting Duo. */
+ @NonnullAfterInit private HttpClient httpClient;
+
+ /** HTTP client security parameters. */
+ @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (httpClient == null) {
+ throw new ComponentInitializationException("httpClient cannot be null");
+ }
+ }
+
+ /**
+ * Set the {@link HttpClient} to use.
+ *
+ * @param client client to use
+ */
+ public void setHttpClient(@Nonnull final HttpClient client) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
+ }
+
+ /**
+ * Set the optional client security parameters.
+ *
+ * @param params the new client security parameters
+ */
+ public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ httpClientSecurityParameters = params;
+ }
+
+
+ /**
+ * Performs a call to an OIDC endpoint that expects a message in the body of the response.
+ * Iff successful, the JSON response is mapped into the appropriate type.
+ *
+ * @param request the prepared HTTP request
+ *
+ * @return the response, never {@code null}.
+ *
+ * @throws IOException if there is an error producing a response
+ */
+ @Nonnull protected HttpResponse executeHttpRequest(@Nonnull final HttpUriRequest request) throws IOException {
+
+ Constraint.isNotNull(request, "Request can not be null");
+
+ final HttpClientContext clientContext = HttpClientContext.create();
+ HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, true);
+ HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
+ final HttpResponse httpResponse = httpClient.execute(request, clientContext);
+ HttpClientSecuritySupport.checkTLSCredentialEvaluated(clientContext, request.getURI().getScheme());
+
+ return httpResponse;
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
new file mode 100644
index 0000000..d7b4d9d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCAuthenticationResponseAction.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+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 org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ *
+ * Abstract class for actions performing actions on {@link AuthenticationResponse} located under
+ * {@link ProfileRequestContext#getOutboundMessageContext()#getM}.
+ *
+ * FIXME: this is very close to that in the OP AbstractOIDCAuthenticationResponseAction. Maybe consolidate
+ * TODO create one of these for the outbound phase (auth request) and one for the other phases which will
+ * include the response.
+ */
+abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
+
+ /** Lookup strategy to locate the OP metadata to use.*/
+ @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+
+ /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+ @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+
+ /** OIDC authentication response from downstream OP. */
+ @Nullable private AuthenticationSuccessResponse authnResponse;
+
+ /** OIDC authentication request built by the IdP. */
+ @Nullable private OIDCAuthenticationRequest authnRequest;
+
+ /** OIDC client Metadata context. */
+ @Nullable private OIDCClientMetadataContext clientMetadataContext;
+
+ /** OIDC Metadata context. */
+ @Nullable private OIDCProviderMetadataContext providerMetadataContext;
+
+ /** Constructor.*/
+ public AbstractOIDCAuthenticationAction() {
+ providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+ new OutboundMessageContextLookup());
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID providers metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID client metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setClientMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Returns the authentication response from the downstream OP.
+ *
+ * @return the authentication response.
+ */
+ @Nullable public AuthenticationSuccessResponse getAuthenticationResponse() {
+ return authnResponse;
+ }
+
+ /**
+ * Returns the authentication request build by this IdP.
+ *
+ * @return the authentication request.
+ */
+ @Nullable public OIDCAuthenticationRequest getAuthenticationRequest() {
+ return authnRequest;
+ }
+
+ /**
+ * Returns the client metadata context.
+ *
+ * @return The client metadata context.
+ */
+ @Nullable public OIDCClientMetadataContext getClientMetadataContext() {
+ return clientMetadataContext;
+ }
+
+ /**
+ * Returns the OIDC provider metadata context.
+ *
+ * @return The provider metadata context.
+ */
+ @Nullable public OIDCProviderMetadataContext getProviderMetadataContext() {
+ return providerMetadataContext;
+ }
+
+ //TODO these come from the outbound message context, so are not general to other actions
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+
+ final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+ if (inboundMessageCtx == null) {
+ log.error("{} No inbound message context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ if (inboundMessageCtx.getMessage() == null) {
+ log.error("{} No inbound message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ if (!(inboundMessageCtx.getMessage() instanceof AuthenticationSuccessResponse)) {
+ log.error("{} No inbound authentication success response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ authnResponse = (AuthenticationSuccessResponse)inboundMessageCtx.getMessage();
+
+ final MessageContext outboundMsgContext = profileRequestContext.getOutboundMessageContext();
+ if (outboundMsgContext == null) {
+ log.error("{} Outbound message context was null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ if (!(outboundMsgContext.getMessage() instanceof OIDCAuthenticationRequest)) {
+ log.error("{} Outbound message was not an authentication request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
+
+ clientMetadataContext = clientMetadataLookupStrategy.apply(profileRequestContext);
+ if (clientMetadataContext == null) {
+ log.error("{} No client metadata found for relying party", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ providerMetadataContext = providerMetadataLookupStrategy.apply(profileRequestContext);
+ if (providerMetadataContext == null) {
+ log.error("{} No provider metadata found for peer", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
index 4d7511c..871c634 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
@@ -17,32 +17,34 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-import java.net.URI;
-import java.net.URISyntaxException;
+import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
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.profile.context.navigate.InboundMessageContextLookup;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.nimbusds.oauth2.sdk.ResponseType;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
/**
@@ -70,15 +72,75 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
@Nullable private OIDCAuthorizationConfiguration profileConfiguration;
/** The metadata belonging to the client registered with the current OpenID Provider.*/
- @Nullable private OIDCMetadataContext clientMetadata;
+ @Nullable private OIDCClientMetadataContext clientMetadata;
/** The metadata belonging to the OpenID Provider.*/
@Nullable private OIDCProviderMetadataContext providerMetadata;
+ /** The response type and mode context.*/
+ @Nullable private ResponseTypeAndModeContext responseTypeAndModeContext;
+
+ /** Lookup strategy to locate the OP metadata to use.*/
+ @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+
+ /** Lookup strategy to locate the metadata of the client to use to communicate with the OP.*/
+ @Nonnull private Function<ProfileRequestContext,OIDCClientMetadataContext> clientMetadataLookupStrategy;
+
+ /** Lookup strategy to locate the response_mode and response_type context.*/
+ @Nonnull private Function<ProfileRequestContext,ResponseTypeAndModeContext> responseTypeAndModeLookupStrategy;
+
/** Constructor.*/
public AddAuthzRequest() {
// Fool the parent class into looking above instead of below the PRC for the context.
setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+
+ providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new InboundMessageContextLookup()));
+
+ clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+ new InboundMessageContextLookup());
+
+ responseTypeAndModeLookupStrategy = new ChildContextLookup<>(ResponseTypeAndModeContext.class).compose(
+ new OutboundMessageContextLookup());
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID providers metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID client metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setClientMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate the response type and mode context.
+ *
+ * @param strategy the strategy.
+ */
+ public void setResponseTypeAndModeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, ResponseTypeAndModeContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ responseTypeAndModeLookupStrategy = Constraint.isNotNull(strategy,
+ "ResponseTypeAndMode context lookup strategy can not be null");
}
/**
@@ -100,6 +162,8 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
return false;
}
+
+ // TODO we need to do something with the rpCtx.
final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
if (rpCtx != null && rpCtx.getConfiguration() != null &&
rpCtx.getProfileConfig() instanceof OIDCAuthorizationConfiguration) {
@@ -122,21 +186,27 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
return false;
}
- final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
- clientMetadata = inboundMessageCtx.getSubcontext(OIDCMetadataContext.class);
+ responseTypeAndModeContext = responseTypeAndModeLookupStrategy.apply(profileRequestContext);
+ if (responseTypeAndModeContext == null) {
+ log.error("{} ResponseTypeContext not found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return false;
+ }
+
+ clientMetadata = clientMetadataLookupStrategy.apply(profileRequestContext);
if (clientMetadata == null) {
log.debug("{} Inbound message context did not contain client metadata", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
- providerMetadata = inboundMessageCtx.getSubcontext(OIDCProviderMetadataContext.class);
+ providerMetadata = providerMetadataLookupStrategy.apply(profileRequestContext);
if (providerMetadata == null) {
log.debug("{} Inbound message context did not contain provider metadata", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
- outboundMessageCtx.setMessage(null);
+ profileRequestContext.getOutboundMessageContext().setMessage(null);
return true;
}
@@ -151,10 +221,19 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
- request.setResponseType(ResponseType.CODE);
+ request.setResponseType(responseTypeAndModeContext.getResponseType());
+ //TODO spec says response mode not recommended if the default type for response_type. Check here?
+ request.setResponseMode(responseTypeAndModeContext.getResponseMode());
request.setEndpointURI(providerMetadata.getProviderInformation().getAuthorizationEndpointURI());
request.setRedirectURI(clientMetadata.getClientInformation().getMetadata().getRedirectionURI());
+ //TODO if force-authn
+// try {
+// request.setPrompt(Prompt.parse("none"));
+// } catch (ParseException e) {
+// log.error("{} Unable to set prompt", e);
+// }
+
log.debug("{} Built authorization request for endpoint '{}'",getLogPrefix(), request.getEndpointURI());
profileRequestContext.getOutboundMessageContext().setMessage(request);
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
index d9a17f6..137789d 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
@@ -27,25 +27,34 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
+import org.opensaml.messaging.context.MessageContext;
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.messaging.context.SAMLMessageReceivedEndpointContext;
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.RequestMapping;
+import com.nimbusds.oauth2.sdk.AuthorizationResponse;
import com.nimbusds.oauth2.sdk.id.State;
import net.shibboleth.idp.authn.ExternalAuthentication;
import net.shibboleth.idp.authn.ExternalAuthenticationException;
import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCProxyException;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
/**
*
@@ -73,6 +82,12 @@ import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
@RequestMapping("%{shibboleth.authn.OIDC.externalAuthnPath:/Authn/OIDC/RP}")
public class AuthorizationController {
+ /** The name of the Http parameter that stores the authorisation code.*/
+ @Nonnull @NotEmpty public static final String CODE_PARAMETER = "code";
+
+ /** The name of the Http parameter that stores the state value.*/
+ @Nonnull @NotEmpty public static final String STATE_PARAMETER = "state";
+
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
@@ -106,7 +121,8 @@ public class AuthorizationController {
*/
@GetMapping("/authz")
public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest,
- @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException, ExternalAuthenticationException {
+ @Nonnull final HttpServletResponse httpResponse)
+ throws ServletException, IOException, ExternalAuthenticationException {
final String key = ExternalAuthentication.startExternalAuthentication(httpRequest);
final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
@@ -169,42 +185,79 @@ public class AuthorizationController {
* @param httpRequest the servlet request.
* @param httpResponse the servlet response.
*
- * @throws ServletException throw if there is an error accepting the authz response.
+ * @throws ExternalAuthenticationException throw if there is an error accepting the authz response.
* @throws IOException throw if there is an error accepting the authz response.
*/
@GetMapping("/callback")
public void authorizationCallback(@Nonnull final HttpServletRequest httpRequest,
- @Nonnull final HttpServletResponse httpResponse) throws ServletException, IOException {
-
+ @Nonnull final HttpServletResponse httpResponse) throws ExternalAuthenticationException, IOException {
+
+ // We REQUIRE state to be passed in the request and response in order to resume the flow.
+ final String state = httpRequest.getParameter(STATE_PARAMETER);
+
+ log.debug("OIDC Relying Party Proxy recieved callback with state '{}'", state);
+
+ if (state == null) {
+ throw new ExternalAuthenticationException("OIDC authentication response must contain a 'state' parameter");
+ }
+
+ final String key;
try {
-// final HttpSession session = httpRequest.getSession();
-// if (session == null) {
-// throw new ExternalAuthenticationException("No session exists, this URL shouldn't be called directly");
-// }
-// final String key = StringSupport.trimOrNull((String) httpRequest.getSession()
-// .getAttribute(AuthorizationController.SESSION_ATTR_FLOWKEY));
-// if (key == null) {
-// throw new ExternalAuthenticationException(
-// "Could not find value for " + AuthorizationController.SESSION_ATTR_FLOWKEY);
-// }
-// final OpenIDConnectContext openIDConnectContext =
-// (OpenIDConnectContext) httpRequest.getSession()
-// .getAttribute(AuthorizationController.SESSION_ATTR_SUCTX);
-// if (openIDConnectContext == null) {
-// throw new ExternalAuthenticationException(
-// "Could not find value for " + AuthorizationController.SESSION_ATTR_SUCTX);
-// }
-// log.trace("Attempting URL {}?{}", httpRequest.getRequestURL(), httpRequest.getQueryString());
-// try {
-// openIDConnectContext.setAuthenticationResponseURI(httpRequest);
-// } catch (final URISyntaxException e) {
-// throw new ExternalAuthenticationException("Could not parse response URI", e);
-// }
- ExternalAuthentication.finishExternalAuthentication("key-wrong", httpRequest, httpResponse);
- } catch (final ExternalAuthenticationException e) {
- log.error("Could not finish the external authentication", e);
- throw new ServletException("Error finishing the external authentication", e);
+ key = OIDCProxySupport.extractKeyFromState(state);
+ } catch (final OIDCProxyException e) {
+ throw new ExternalAuthenticationException("Flow execution key component could not be found in the "
+ + "returned state, unable to resume the flow execution",e);
+ }
+
+ final ProfileRequestContext prc = ExternalAuthentication.getProfileRequestContext(key, httpRequest);
+ final OIDCAuthnContext oidcContext = oidcContextLookupStrategy.apply(prc);
+ if (oidcContext == null) {
+ log.error("OIDCAuthnContext not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
}
+ final ProfileRequestContext nestedPRC = profileRequestContextLookupStrategy.apply(prc);
+ if (nestedPRC == null) {
+ log.error("Nested ProfileRequestContext not found");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+ // Find which response mode to expect.
+ final ResponseTypeAndModeContext responseCtx =
+ nestedPRC.getOutboundMessageContext().getSubcontext(ResponseTypeAndModeContext.class);
+ if (responseCtx == null) {
+ log.error("ResponseTypeAndMode context not found, cannot decode incomming request");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+ log.debug("OIDC response_type '{}' and response_mode '{}' requested, decoding incoming request",
+ responseCtx.getResponseMode(), responseCtx.getResponseType());
+
+ try {
+ final MessageDecoder decoder =
+ oidcContext.getMessageDecoderFactory().apply(responseCtx.getResponseMode().getValue());
+ if (decoder == null) {
+ throw new MessageDecodingException("Unable to obtain MessageDecoder for response_mode: "
+ + responseCtx.getResponseMode().getValue());
+ }
+ try {
+ decoder.initialize();
+ decoder.decode();
+ final MessageContext messageContext = decoder.getMessageContext();
+ nestedPRC.setInboundMessageContext(messageContext);
+ } finally {
+ decoder.destroy();
+ }
+ } catch (final MessageDecodingException | ComponentInitializationException e) {
+ log.error("Unable to decode OIDC response", e);
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.UNABLE_TO_DECODE);
+ }
+
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+
}
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java
new file mode 100644
index 0000000..ed531df
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseModeLookupFunction.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+/**
+ * A lookup function that returns a response_mode compatible with the response_type described.
+ * The response_mode can be overriden by the profile configuration.
+ */
+public class DefaultResponseModeLookupFunction
+ implements BiFunction<ProfileRequestContext, ResponseType, ResponseMode> {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(DefaultResponseModeLookupFunction.class);
+
+ @Override
+ @Nonnull public ResponseMode apply(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final ResponseType responseType) {
+
+// final RelyingPartyContext rpCtx = profileRequestContext.getSubcontext(RelyingPartyContext.class);
+// OIDCAuthorizationConfiguration profileConfiguration = null;
+//
+// if (rpCtx != null && rpCtx.getProfileConfig() instanceof OIDCAuthorizationConfiguration) {
+// profileConfiguration = (OIDCAuthorizationConfiguration) rpCtx.getProfileConfig();
+//
+// final OIDCHttpRequestMethod requestMethodFromConfig =
+// profileConfiguration.getHttpRequestMethod(profileRequestContext);
+// }
+ //TODO extract possible response mode from profile config as well. Must be compatible with the response_type
+ final ResponseMode compatibleMode = ResponseMode.resolve(null, responseType);
+ log.debug("Response mode '{}' resolved from response type '{}'", compatibleMode, responseType);
+ return compatibleMode;
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java
new file mode 100644
index 0000000..e08eacf
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultResponseTypeLookupFunction.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+
+/**
+ * A lookup function that returns a response_type from the 'first' described in a client's metadata.
+ * If none are found, the default response type of 'code' is returned.
+ */
+public class DefaultResponseTypeLookupFunction implements Function<ProfileRequestContext, ResponseType> {
+
+ /** The Default response type if none is selected.*/
+ @Nonnull private static final ResponseType DEFAULT_RESPONSE_TYPE = ResponseType.CODE;
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(DefaultResponseTypeLookupFunction.class);
+
+ @Override
+ @Nonnull public ResponseType apply(@Nullable final ProfileRequestContext profileRequestContext) {
+
+ if (profileRequestContext == null) {
+ return DEFAULT_RESPONSE_TYPE;
+ }
+
+ final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
+ final OIDCClientMetadataContext clientMetadataCtx =
+ inboundMessageCtx.getSubcontext(OIDCClientMetadataContext.class);
+ if (clientMetadataCtx != null &&
+ clientMetadataCtx.getClientInformation() != null &&
+ clientMetadataCtx.getClientInformation().getOIDCMetadata() != null &&
+ clientMetadataCtx.getClientInformation().getOIDCMetadata().getResponseTypes() != null) {
+
+ final ResponseType selectedResponseType =
+ clientMetadataCtx.getClientInformation().getOIDCMetadata().getResponseTypes().iterator().next();
+ log.debug("Response_type has been determined from client metadata as '{}'", selectedResponseType);
+ return selectedResponseType;
+ }
+
+ log.debug("Client metadata did not contain any configured response_types, "
+ + "returning the default '{}' type", DEFAULT_RESPONSE_TYPE);
+ return DEFAULT_RESPONSE_TYPE;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java
new file mode 100644
index 0000000..2fc953d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForToken.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpUriRequest;
+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 org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+
+/**
+ * Action to exchange the authorization code in the authentication response for a token response which
+ * contains an id_token. Once obtained, adds the token to the {@link TokenResponseContext}.
+ *
+ * FIXME: these conditions
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null and
+ * AuthenticationContext.getSubcontect(DuoOIDCAuthenticationContext.class,false)!=null</pre>
+ * @post Add the Duo authentication token to the context.
+ */
+public class ExchangeCodeForToken extends AbstractHttpOIDCAuthenticationAction {
+
+ /** Class logger.*/
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExchangeCodeForToken.class);
+
+ /** The message encoder to use encode the token request into a HttpRequest.*/
+ @NonnullAfterInit private Function<ProfileRequestContext, HttpUriRequest> tokenRequestEncoderStrategy;
+
+ /** The message decoder to use decode a HTTP response to a token object.*/
+ @NonnullAfterInit private Function<HttpResponse, Map<String, Object>> tokenResponseDecoderStrategy;
+
+ /** Strategy used to look up the {@link TokenResponseContext} to set the parameters for. */
+ @Nonnull private Function<ProfileRequestContext, TokenResponseContext>
+ tokenResponseContextLookupStrategy;
+
+ /** Constructor.*/
+ public ExchangeCodeForToken() {
+ tokenResponseContextLookupStrategy =
+ new ChildContextLookup<>(TokenResponseContext.class, true).compose(
+ new InboundMessageContextLookup());
+ }
+
+ /**
+ * Set the strategy used to look up a {@link TokenResponseContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setTokenResponseContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, TokenResponseContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+ "TokenResponseContext lookup strategy cannot be null");
+ }
+
+
+ /**
+ * Set the strategy used to map a request...TODO.
+ *
+ * @param strategy the strategy
+ */
+ public void setTokenResponseDecoderStrategy(@Nonnull final Function<HttpResponse, Map<String, Object>> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ tokenResponseDecoderStrategy = Constraint.isNotNull(strategy, "Token decoder strategy can not be null");
+ }
+
+ /**
+ * Set the strategy used to map a request...TODO.
+ *
+ * @param strategy the strategy
+ */
+ public void setTokenRequestEncoderStrategy(
+ @Nonnull final Function<ProfileRequestContext, HttpUriRequest> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ tokenRequestEncoderStrategy = Constraint.isNotNull(strategy, "Token encoder strategy can not be null");
+ }
+
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ log.debug("{} Exchanging auth_code '{}' for id_token from upstream OP '{}'", getLogPrefix(),
+ getAuthenticationResponse().getAuthorizationCode(),
+ authenticationContext.getAuthenticatingAuthority());
+
+ final TokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+ if (responseCtx == null) {
+ log.debug("{} No TokenResponseContext returned by lookup strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ try {
+ final HttpUriRequest request = tokenRequestEncoderStrategy.apply(profileRequestContext);
+ if (request == null) {
+ log.warn("{} Unable to exhange authorization_code for token, request could "
+ + "not be constructed", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+ return;
+ }
+ final HttpResponse response = executeHttpRequest(request);
+ final Map<String, Object> responseObject = tokenResponseDecoderStrategy.apply(response);
+ if (responseObject == null) {
+ log.warn("{} Unable to exhange authorization_code for token, response could "
+ + "not be decoded", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+ return;
+ }
+ responseCtx.setRawTokenResponse(responseObject);
+ log.debug("{}: Token request response '{}'",getLogPrefix(), responseObject);
+
+ } catch (final IOException e) {
+ log.error("{} Unable to exchange authorisation code for token result",getLogPrefix(),e);
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
+ }
+ }
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java
new file mode 100644
index 0000000..c286ec7
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExtractIDTokenFromResponse.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.JOSEObject;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.JWSObject;
+import com.nimbusds.jose.PlainObject;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** Action that extracts and id_token from the inbound message context and sets it onto a TODO context.*/
+public class ExtractIDTokenFromResponse extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractIDTokenFromResponse.class);
+
+ /** Function that looks up a raw id_token value as a base64 encoded string.*/
+ @NonnullAfterInit private Function<ProfileRequestContext, String> rawIdTokenLookupStrategy;
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (rawIdTokenLookupStrategy == null) {
+ throw new ComponentInitializationException("RawIdTokenLookupStrategy cannot be null");
+ }
+ }
+
+ /**
+ * Set the strategy used to lookup a base64 encoded JWT from the profile request context.
+ *
+ * @param strategy the strategy to use.
+ */
+ public void setRawIdTokenLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ rawIdTokenLookupStrategy = Constraint.isNotNull(strategy,"Raw id_token lookup strategy can not be null");
+ }
+
+ @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ return true;
+ }
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String rawIdTokenValue = rawIdTokenLookupStrategy.apply(profileRequestContext);
+ if (rawIdTokenValue == null) {
+ log.warn("{} Unable to lookup id_token",getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
+ return;
+ }
+ log.trace("{} Base64 encoded id_token is '{}'",getLogPrefix(), rawIdTokenValue);
+ try {
+ final JOSEObject joseObject = JOSEObject.parse(rawIdTokenValue);
+ log.trace("{} Parsed JOSE Object '{}'",getLogPrefix(), joseObject);
+ if (joseObject instanceof PlainObject) {
+ final PlainObject plainObject = (PlainObject)joseObject;
+
+ } else if (joseObject instanceof JWSObject) {
+ final JWSObject jwsObject = (JWSObject)joseObject;
+
+ } else if (joseObject instanceof JWEObject) {
+ final JWEObject jweObject = (JWEObject)joseObject;
+
+ }
+
+ } catch (final ParseException e) {
+ log.warn("{}: Unable to parse id_token",getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext,OidcEventIds.INVALID_ID_TOKEN);
+ return;
+
+ }
+
+ }
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
deleted file mode 100644
index d4b047d..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/GetOIDCTokenResponse.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.io.IOException;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractExtractionAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.oauth2.sdk.AuthorizationCode;
-import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
-import com.nimbusds.oauth2.sdk.AuthorizationGrant;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.SerializeException;
-import com.nimbusds.oauth2.sdk.TokenErrorResponse;
-import com.nimbusds.oauth2.sdk.TokenRequest;
-import com.nimbusds.oauth2.sdk.TokenResponse;
-import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
-import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
-import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponseParser;
-
-/**
- * An action that exchanges the OAuth 2.0 authorization code inside the {@link OpenIDConnectContext} for
- * an id_token from the OpenID Connect Provider's Token Endpoint. The ID Token is placed inside the
- * {@link OpenIDConnectContext}.
- *
- *
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @post If getIDToken() !=null the method returns immediately. Otherwise, if the token endpoint returns an
- * {@link OIDCTokenResponse} whose indicatesSuccess()==true, the token is attached to the {@link OpenIDConnectContext}.
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- *
- * @since 4.0.0
- */
-public class GetOIDCTokenResponse extends AbstractExtractionAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(GetOIDCTokenResponse.class);
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
- final OpenIDConnectContext oidcCtx =
- authenticationContext.getSubcontext(OpenIDConnectContext.class);
- if (oidcCtx == null) {
- log.error("{} Unable to find OIDC context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
- return;
- }
- if (oidcCtx.getIDToken() != null) {
- log.debug("id_token already exists, nothing to fetch from token endpoint");
-
- return;
- }
- final AuthenticationSuccessResponse response = oidcCtx.getAuthenticationSuccessResponse();
-
- if (response == null) {
- log.info("{} Authentication success response not found in OpenIDConnectContext", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
- return;
- }
- final AuthorizationCode code = response.getAuthorizationCode();
- //TODO P.S. code could be null, should not be.
- final AuthorizationGrant codeGrant = new AuthorizationCodeGrant(code, oidcCtx.getRedirectURI());
- //TODO P.S. neither should be null, both could be (although not after SetOIDCInformation initilises the context)
- final ClientAuthentication clientAuth = new ClientSecretBasic(oidcCtx.getClientID(), oidcCtx.getClientSecret());
-
- log.trace("{} Using the following OIDC token endpoint URI: {}", getLogPrefix(),
- oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI());
-
- final TokenRequest tokenRequest =
- new TokenRequest(oidcCtx.getoIDCProviderMetadata().getTokenEndpointURI(), clientAuth, codeGrant);
- final OIDCTokenResponse oidcTokenResponse;
- try {
- final TokenResponse tokenResponse = OIDCTokenResponseParser.parse(tokenRequest.toHTTPRequest().send());
- // TokenResponse can only be TokenErrorResponse or OIDCTokenResponse
- if (tokenResponse instanceof OIDCTokenResponse) {
-
- oidcTokenResponse = (OIDCTokenResponse) tokenResponse;
- oidcCtx.setOidcTokenResponse(oidcTokenResponse);
-
- if (log.isTraceEnabled() && oidcTokenResponse.getOIDCTokens().getIDToken() != null) {
- log.trace("{} Retrieved id_token '{}'",getLogPrefix(),
- oidcTokenResponse.getOIDCTokens().getIDToken().getParsedString());
- }
-
- } else {
- final TokenErrorResponse errorResponse = (TokenErrorResponse) tokenResponse;
- log.warn("{} Error in retrieving id_token, response error is {}", getLogPrefix(),
- errorResponse.getErrorObject());
- // should map error object OAuth2 Error types to new or existing event ids.
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
- return;
- }
-
- } catch (final SerializeException | IOException | ParseException e) {
- log.error("{} token exchange failed", getLogPrefix(), e);
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
- return;
- }
-
-
- }
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
index 2296d48..0141ca1 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOutboundAuthorizationRequestMessageContext.java
@@ -20,7 +20,9 @@ package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.util.function.Function;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opensaml.messaging.context.BaseContext;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.profile.action.ActionSupport;
@@ -30,8 +32,11 @@ import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -41,39 +46,42 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
/** Class logger. */
@Nonnull
- private final Logger log = LoggerFactory.getLogger(InitializeOutboundAuthorizationRequestMessageContext.class);
+ private final Logger log = LoggerFactory.getLogger(InitializeOutboundAuthorizationRequestMessageContext.class);
- /** Strategy function to lookup the {@link OIDCMetadataContext}. */
- @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataCtxLookupStrategy;
+ /** The {@link OIDCPeerEntityContext} to base the outbound context on. */
+ @Nullable private OIDCPeerEntityContext peerEntityCtx;
+
+ /** Strategy function to lookup the {@link OIDCClientMetadataContext} that represents this client during
+ * communication with the given OIDC peer. */
+ @Nonnull
+ private Function<ProfileRequestContext, OIDCClientMetadataContext> oidcClientMetadataCtxLookupStrategy;
/**
* Strategy used to locate the {@link RelyingPartyContext} associated with a given {@link ProfileRequestContext}.
*/
@Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyCtxLookupStrategy;
-
- /** The relying party context used for storing the SAML metadata context. */
- //TODO do we need this one? or just the OIDC provider metadata context?
- private RelyingPartyContext relyingPartyCtx;
+
/**
* Constructor.
*/
public InitializeOutboundAuthorizationRequestMessageContext() {
- oidcProviderMetadataCtxLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
- new InboundMessageContextLookup());
+ oidcClientMetadataCtxLookupStrategy = new ChildContextLookup<>(OIDCClientMetadataContext.class).compose(
+ new InboundMessageContextLookup());
relyingPartyCtxLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
}
/**
- * Set the strategy to lookup the {@link oidcProviderMetadataCtxLookupStrategy} from the {@link ProfileRequestContext}.
+ * Set the strategy to lookup the {@link oidcProviderMetadataCtxLookupStrategy}
+ * from the {@link ProfileRequestContext}.
*
* @param strgy What to set.
*/
- public void setOIDCProviderMetadataContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strgy) {
+ public void setOIDCClientMetadataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCClientMetadataContext> strgy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- oidcProviderMetadataCtxLookupStrategy = Constraint.isNotNull(strgy, "Injected Metadata Strategy cannot be null");
+ oidcClientMetadataCtxLookupStrategy = Constraint.isNotNull(strgy, "Injected Metadata Strategy cannot be null");
}
/**
@@ -98,13 +106,22 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
return false;
}
- relyingPartyCtx = relyingPartyCtxLookupStrategy.apply(profileRequestContext);
+ final RelyingPartyContext relyingPartyCtx = relyingPartyCtxLookupStrategy.apply(profileRequestContext);
if (relyingPartyCtx == null) {
log.error("{} No relying party context", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
+ final BaseContext identifyingCtx = relyingPartyCtx.getRelyingPartyIdContextTree();
+ if (!(identifyingCtx instanceof OIDCPeerEntityContext)) {
+ log.debug("{} No OIDC peer entity context found via relying party context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
+ return false;
+ }
+
+ peerEntityCtx = (OIDCPeerEntityContext) identifyingCtx;
+
return true;
}
@@ -115,7 +132,28 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
final MessageContext msgCtx = new MessageContext();
profileRequestContext.setOutboundMessageContext(msgCtx);
- //msgCtx.addSubcontext(new OIDCAuthorizationRequestContext());
+
+ final OIDCPeerEntityContext outboundPeerContext = msgCtx.getSubcontext(OIDCPeerEntityContext.class, true);
+ outboundPeerContext.setIdentifier(peerEntityCtx.getIdentifier());
+
+ final OIDCProviderMetadataContext inboundProviderMetadata =
+ peerEntityCtx.getSubcontext(OIDCProviderMetadataContext.class);
+ if (inboundProviderMetadata != null) {
+
+ // Pass a reference here? we do not need to mutate the metadata
+ final OIDCProviderMetadataContext outMetadata =
+ new OIDCProviderMetadataContext(inboundProviderMetadata.getProviderInformation());
+
+ outboundPeerContext.addSubcontext(outMetadata);
+
+ }
+ final OIDCClientMetadataContext inboundClientMetadata =
+ oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
+
+ final OIDCClientMetadataContext outboundClientMetadata = new OIDCClientMetadataContext();
+ outboundClientMetadata.setClientInformation(inboundClientMetadata.getClientInformation());
+ msgCtx.addSubcontext(outboundClientMetadata);
+
log.debug("{} Initialized outbound message context", getLogPrefix());
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
index 3d1376e..ac3bc54 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeRelyingPartyContext.java
@@ -22,17 +22,18 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import org.opensaml.messaging.context.MessageContext;
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 org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.DefaultIssuerIDLookupFunction;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
@@ -57,11 +58,15 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
/** Strategy that will return or create a {@link RelyingPartyContext}. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyContext> relyingPartyContextCreationStrategy;
- /** Strategy that will return {@link OIDCMetadataContext}. */
- @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataContextLookupStrategy;
+ /** Strategy that will return {@link OIDCClientMetadataContext}. */
+ @Nonnull
+ private Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataContextLookupStrategy;
+
+ /** OIDC peer entity context to populate from. */
+ @Nullable private OIDCPeerEntityContext peerEntityCtx;
- /** Strategy used to obtain the client id value for authorize/token request. */
- @Nonnull private Function<MessageContext, String> issuerIDLookupStrategy;
+ /** Strategy used to look up the {@link SAMLPeerEntityContext} to draw from. */
+ @Nonnull private Function<ProfileRequestContext,OIDCPeerEntityContext> peerEntityContextLookupStrategy;
/** OIDC issuer id of the downstream OP. */
@Nullable private String issuerId;
@@ -69,20 +74,27 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
/** Constructor. */
public InitializeRelyingPartyContext() {
relyingPartyContextCreationStrategy = new ChildContextLookup<>(RelyingPartyContext.class, true);
+
oidcProviderMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
- new InboundMessageContextLookup());
- issuerIDLookupStrategy = new DefaultIssuerIDLookupFunction();
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new InboundMessageContextLookup()));
+
+ peerEntityContextLookupStrategy =
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new InboundMessageContextLookup());
}
/**
- * Set the strategy used to locate the issuer id of the request.
+ * Set the strategy used to look up the {@link OIDCPeerEntityContext} to draw from.
*
- * @param strategy lookup strategy
+ * @param strategy strategy used to look up the {@link OIDCPeerEntityContext}
*/
- public void setIssuerIDLookupStrategy(@Nonnull final Function<MessageContext, String> strategy) {
+ public void setPeerEntityContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,OIDCPeerEntityContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- issuerIDLookupStrategy =
- Constraint.isNotNull(strategy, "IssuerID lookup strategy cannot be null");
+
+ peerEntityContextLookupStrategy =
+ Constraint.isNotNull(strategy, "OIDCPeerEntityContext lookup strategy cannot be null");
}
/**
@@ -111,18 +123,21 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
Constraint.isNotNull(strategy, "OIDCMetadataContext lookup strategy cannot be null");
}
+
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
if (!super.doPreExecute(profileRequestContext)) {
log.error("{} pre-execute failed", getLogPrefix());
return false;
}
- issuerId = issuerIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
- if (issuerId == null) {
- log.error("{} Unable to locate issuer id from the request", getLogPrefix());
+
+ peerEntityCtx = peerEntityContextLookupStrategy.apply(profileRequestContext);
+ if (peerEntityCtx == null) {
+ log.warn("{} Unable to locate OIDCPeerEntityContext", getLogPrefix());
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
+
return true;
}
@@ -135,11 +150,13 @@ public class InitializeRelyingPartyContext extends AbstractProfileAction {
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
return;
}
- log.debug("Attaching RelyingPartyContext for OP {}", issuerId);
- rpContext.setRelyingPartyId(issuerId);
- final OIDCProviderMetadataContext oidcContext = oidcProviderMetadataContextLookupStrategy.apply(profileRequestContext);
+ log.debug("Attaching RelyingPartyContext for OP {}", peerEntityCtx.getIdentifier());
+ rpContext.setRelyingPartyId(peerEntityCtx.getIdentifier());
+ rpContext.setRelyingPartyIdContextTree(peerEntityCtx);
+ final OIDCProviderMetadataContext oidcContext =
+ oidcProviderMetadataContextLookupStrategy.apply(profileRequestContext);
if (oidcContext != null && oidcContext.getProviderInformation() != null
- && issuerId.equals(oidcContext.getProviderInformation().getIssuer().getValue())) {
+ && peerEntityCtx.getIdentifier().equals(oidcContext.getProviderInformation().getIssuer().getValue())) {
log.debug("{} Setting the OP context to 'verified'", getLogPrefix());
rpContext.setVerified(true);
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java
index 54089e1..f29bd12 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCAuthenticationTimeRequested.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.util.function.Predicate;
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
index ed433e2..c9069cc 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCContextAudienceClaimLookupStrategy.java
@@ -36,7 +36,8 @@ import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
* Returns null if it fails to find the clientID. Used for JWT ID Token audience claims verification.
*/
@ThreadSafe
-public final class OIDCContextAudienceClaimLookupStrategy implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+public final class OIDCContextAudienceClaimLookupStrategy
+ implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
@Override @Nullable public String apply(@Nonnull final ProfileRequestContext context,
@Nonnull final JWTClaimsSet cliams) {
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
index c214c76..c888feb 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCProxySupport.java
@@ -1,17 +1,36 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.security.SecureRandom;
import javax.annotation.Nonnull;
+import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
+import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCProxyException;
import net.shibboleth.utilities.java.support.logic.Constraint;
/**
* Support class for OIDC proxy related implementations.
*/
-//TODO Duo has similar support class methods, maybe merge.
+//TODO Duo has similar support class methods, maybe merge into commons.
public final class OIDCProxySupport {
/** Private constructor.*/
@@ -57,5 +76,52 @@ public final class OIDCProxySupport {
final String keyHex = Hex.encodeHexString(key.getBytes());
return nonce+"."+keyHex;
}
+
+ /**
+ * Extract the key component from the state. The key is hex encoded and separated from the
+ * nonce value by a dot.
+ *
+ * @param state the state which contains both the nonce and the key dot separated.
+ *
+ * @return the key extracted from the state and hex decoded.
+ *
+ * @throws OIDCProxyException if the key component can not be found, or hex decoding fails.
+ */
+ @Nonnull static String extractKeyFromState(@Nonnull final String state) throws OIDCProxyException {
+ Constraint.isNotNull(state, "State can not be null");
+
+ final String[] stateSplit = state.split("\\.");
+ if (stateSplit.length!=2) {
+ throw new OIDCProxyException("State does not contain the key component");
+ }
+ final String hexKey = stateSplit[1];
+ try {
+ //should we check it is sensible?
+ return new String(Hex.decodeHex(hexKey));
+ } catch (final DecoderException e) {
+ throw new OIDCProxyException("Can not hex decode key",e);
+ }
+
+ }
+
+ /**
+ * Extract the nonce component from the state. The nonce is separated from the key
+ * by a dot, and is assumed to be the first value of the pair.
+ *
+ * @param state the state which contains both the nonce and the key dot separated.
+ *
+ * @return the nonce extracted from the state.
+ *
+ * @throws DuoException if the nonce component can not be found.
+ */
+ @Nonnull static String extractNonceFromState(@Nonnull final String state) throws OIDCProxyException {
+ Constraint.isNotNull(state, "State can not be null");
+
+ final String[] stateSplit = state.split("\\.");
+ if (stateSplit.length!=2) {
+ throw new OIDCProxyException("State does not contain the nonce component");
+ }
+ return stateSplit[0];
+ }
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
new file mode 100644
index 0000000..b8f4f99
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PopulateResponseTypeAndModeContext.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that populates the outbound ResponseTypeContext from client metadata or
+ * profile configuration...
+ *
+ * TODO finish
+ */
+public class PopulateResponseTypeAndModeContext extends AbstractProfileAction {
+
+ /** The Default response type if none is selected.*/
+ @Nonnull private static final ResponseType DEFAULT_RESPONSE_TYPE = ResponseType.CODE;
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PopulateResponseTypeAndModeContext.class);
+
+
+ /** Function to lookup the response type given the profile request context.*/
+ @Nonnull private Function<ProfileRequestContext, ResponseType> responseTypeLookup;
+
+ /** Function to lookup the response mode given the profile request context.*/
+ @Nonnull private BiFunction<ProfileRequestContext, ResponseType, ResponseMode> responseModeLookup;
+
+ /** Constructor.*/
+ public PopulateResponseTypeAndModeContext() {
+ responseTypeLookup = new DefaultResponseTypeLookupFunction();
+ responseModeLookup = new DefaultResponseModeLookupFunction();
+ }
+
+ /**
+ * Set the response_type lookup function.
+ *
+ * @param lookup the function.
+ */
+ public void setResponseTypeLookup(@Nonnull final Function<ProfileRequestContext, ResponseType> lookup) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ responseTypeLookup = Constraint.isNotNull(lookup, "Response Type lookup can not be null");
+ }
+
+ /**
+ * Set the response_mode lookup function. Accepts both a chosen response_mode, and the profile request
+ * context.
+ *
+ * @param lookup the function.
+ */
+ public void setResponseModeLookup(
+ @Nonnull final BiFunction<ProfileRequestContext, ResponseType, ResponseMode> lookup) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ responseModeLookup = Constraint.isNotNull(lookup, "Response Mode lookup can not be null");
+ }
+
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ // Fail fast here in case there is no inbound context to pull information from.
+ if (profileRequestContext.getInboundMessageContext() == null) {
+ log.debug("{} Inbound message context was null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final ResponseTypeAndModeContext ctx = new ResponseTypeAndModeContext();
+ ctx.setResponseType(responseTypeLookup.apply(profileRequestContext));
+ ctx.setResponseMode(responseModeLookup.apply(profileRequestContext, ctx.getResponseType()));
+ profileRequestContext.getOutboundMessageContext().addSubcontext(ctx);
+
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java
new file mode 100644
index 0000000..9c45c99
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/PrepareOIDCInboundMessageContext.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Action that adds an inbound {@link MessageContext} and a {@link OIDCPeerEntityContext} to the
+ * {@link ProfileRequestContext} based on the identity of a relying party, by default from the
+ * {@link AuthenticationContext#getAuthenticatingAuthority()}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ */
+public class PrepareOIDCInboundMessageContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareOIDCInboundMessageContext.class);
+
+ /** Strategy to lookup the identifier of the OP/RP to based the inbound context on. */
+ @NonnullAfterInit private Function<ProfileRequestContext,String> identifierLookupStrategy;
+
+ /** The identifier of the OP/RP to base the inbound context on. */
+ @Nullable private String identifier;
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (identifierLookupStrategy == null) {
+ throw new ComponentInitializationException("IdentifierLookupStrategy cannot be null");
+ }
+ }
+
+ /**
+ * Set the lookup strategy to identify the OP/RP.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIdentiferLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ identifierLookupStrategy = Constraint.isNotNull(strategy, "Identifier lookup strategy can not be null");
+ }
+
+ @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ identifier = identifierLookupStrategy.apply(profileRequestContext);
+ if (identifier == null) {
+ log.warn("{} No identifier returned from lookup function", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final MessageContext msgCtx = new MessageContext();
+ profileRequestContext.setInboundMessageContext(msgCtx);
+
+ final OIDCPeerEntityContext peerContext = msgCtx.getSubcontext(OIDCPeerEntityContext.class, true);
+ peerContext.setIdentifier(identifier);
+
+ log.debug("{} Initialized inbound context for message to {}", getLogPrefix(), identifier);
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
deleted file mode 100644
index 021e46a..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/SetOIDCInformation.java
+++ /dev/null
@@ -1,584 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.security.PrivateKey;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.crypto.RSASSASigner;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.PlainJWT;
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.ResponseMode;
-import com.nimbusds.oauth2.sdk.ResponseType;
-import com.nimbusds.oauth2.sdk.Scope;
-import com.nimbusds.oauth2.sdk.auth.Secret;
-import com.nimbusds.oauth2.sdk.id.ClientID;
-import com.nimbusds.oauth2.sdk.id.State;
-import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
-import com.nimbusds.openid.connect.sdk.Display;
-import com.nimbusds.openid.connect.sdk.Nonce;
-import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
-import com.nimbusds.openid.connect.sdk.Prompt;
-import com.nimbusds.openid.connect.sdk.Prompt.Type;
-import com.nimbusds.openid.connect.sdk.claims.ACR;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jose.JWSHeader;
-import com.nimbusds.jose.PlainHeader;
-import com.nimbusds.jwt.SignedJWT;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
-import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.context.AttributeContext;
-import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- *
- * An action that populates the {@link AuthenticationContext} with a freshly built {@link OpenIDConnectContext}.
- *
- * <p>A singleton instance of this class can be created and shared between authentication requests.</p>
- *
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
- * @post The AuthenticationContext is modified as above.
- *
- * @since 4.0.0
- */
- at SuppressWarnings("rawtypes")
-public class SetOIDCInformation extends AbstractAuthenticationAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(SetOIDCInformation.class);
-
- /** Context to look attributes for. */
- @Nonnull private Function<ProfileRequestContext, AttributeContext> attributeContextLookupStrategy;
-
- /** Redirect URI. */
- @Nonnull private URI redirectURI;
-
- /** Client Id. */
- @Nonnull private ClientID clientID;
-
- /** Client Secret. */
- @Nonnull private Secret clientSecret;
-
- /** Response type, default is code flow. */
- @Nonnull private ResponseType responseType = new ResponseType(ResponseType.Value.CODE);
-
- /** Scope. Must contain an openid scope.*/
- @Nonnull private Scope scope = new Scope(OIDCScopeValue.OPENID);
-
- /**
- * OIDC Prompt. Specifies whether the Authorization Server prompts
- * the End-User for reauthentication and consent.
- */
- @Nullable private Prompt prompt;
-
- /** OIDC Authentication Class Reference values. */
- @Nullable @NonnullElements private List<ACR> acrs;
-
- /**
- * OIDC Display. Value that specifies how the Authorization Server
- * displays the authentication and consent user interface pages to the End-User.
- */
- @Nullable private Display display;
-
- /** Private key for signing the request object. */
- @Nullable private PrivateKey signPrvKey;
-
- /** Algorithm used for signing the request object. Defaults to the required
- * singing algorithm of RSA SHA-256*/
- @Nonnull private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
-
- /** key id for key used for signing the request object. */
- @Nonnull private String keyID = "id";
-
- /** Request object claims. */
- @Nullable private Map<String, String> requestClaims;
-
- /** OIDC provider metadata. */
- @Nonnull private OIDCProviderMetadata oIDCProviderMetadata;
-
- /** Constructor. */
- public SetOIDCInformation() {
- attributeContextLookupStrategy =
- new ChildContextLookup<>(AttributeContext.class).compose(
- new ChildContextLookup<>(RelyingPartyContext.class));
-
- }
-
- /**
- * Set the private key used for signing request object.
- *
- * @param key signing key
- */
- public void setPrivKey(@Nullable final PrivateKey key) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- signPrvKey = key;
- }
-
- /**
- * Set the RSA algorithm used for signing the request object. Default is RS256.
- *
- * @param algorithm used for signing, must not be <code>null</code>.
- */
- public void setJwsAlgorithm(@Nonnull final JWSAlgorithm algorithm) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- jwsAlgorithm = Constraint.isNotNull(algorithm,"OpenID Connect JWS Request algorithm can not be null");
-
- }
-
- /**
- * Set the key id of the key.
- *
- * @param id for the key, must not be <code>null</code>.
- */
- public void setKeyID(@Nonnull @NotEmpty final String id) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- keyID =Constraint.isNotNull(StringSupport.trimOrNull(id), "OpenID Connect key ID cannot be null");
- }
-
- /**
- *
- * If this is set (i.e. not <code>null</code>) request claims will be built as a
- * JWT Request Object. See OpenID Connect Core section 6.1. If the <code>signPrvKey</code>
- * is set, the JWT will also be signed.
- *
- * <p>The key of the mapping is the name of the requested claim. The value of the requested claim
- * is either:</p>
- * <ul>
- * <li>Null, if the claim is being requested in a default manor</li>
- * <li><code>{"essential":true}</code> if the value is essential. The default is
- * <code>{"essential":false}</code></li>
- * <li>A specific value for the claim. This value must be a valid value for that claim.</li>
- * <li>A specific set of values for the claim. These values must be valid value for that claim.</li>
- * </ul>
- *
- * <p>
- * If used, the OpenID Connect Provider must support it, as specified in the
- * <code>request_parameter_supported</code> parameter of the Providers discovery metadata.
- * </p>
- *
- * @param claims map of requested claims
- */
- public void setRequestClaims(@Nullable final Map<String, String> claims) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- requestClaims = claims;
- }
-
- /**
- * Sets the response type. Default is code.
- *
- * @param type space-delimited list of one or more authorization response types. Must not be <code>null</code>.
- * @throws ParseException if response type cannot be parsed
- */
- public void setResponseType(@Nonnull @NotEmpty final String type) throws ParseException {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- Constraint.isNotNull(StringSupport.trimOrNull(type), "OpenID Connect response type cannot be null or empty");
-
- responseType = ResponseType.parse(type);
- }
-
- /**
- * Setter for Oauth2 client id.
- *
- * @param oauth2ClientID Oauth2 Client ID, must not be <code>null</code>.
- */
- public void setClientID(@Nonnull @NotEmpty final String oauth2ClientID) {
-
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientID),
- "OpenID Connect client ID cannot be null or empty");
-
- clientID = new ClientID(oauth2ClientID);
- }
-
- /**
- * Setter for OAuth2 Client secret.
- *
- * @param oauth2ClientSecret OAuth2 Client Secret, must not be <code>null</code>.
- */
- public void setClientSecret(@Nonnull @NotEmpty final String oauth2ClientSecret) {
-
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- Constraint.isNotNull(StringSupport.trimOrNull(oauth2ClientSecret),
- "OpenID Connect client secret cannot be null or empty");
-
- clientSecret = new Secret(oauth2ClientSecret);
- }
-
- /**
- * Setter for the OAuth 2.0 redirect URI the OIDC provider will return to. The constructed URI should not be empty.
- *
- * @param redirect OAuth2 redirect uri, must not be <code>null</code>.
- */
-
- public void setRedirectURI(@Nonnull @NotEmpty final URI redirect) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- Constraint.isNotNull(redirect, "OpenID Connect redirect URI cannot be null");
- Constraint.isNotEmpty(redirect.toString(), "OpenID Connect redirect URI cannot be empty");
-
- redirectURI = redirect;
-
- }
-
- /**
- * Setter for OpenId Provider Metadata resource.
- *
- * <p>Constructs a URI from the <code>metadataLocation</code> and attempts to connect,
- * stream, and parse its content into an {@link OIDCProviderMetadata} instance.</p>
- *
- * @param metadataLocation OpenId Provider Metadata location, must not be <code>null</code>.
- *
- * @throws URISyntaxException if metadataLocation is not a URI
- * @throws IOException if metadataLocation cannot be read
- * @throws ParseException if metadataLocation has wrong content
- */
- //TODO: not like this? HttpClient, metadata service etc.
- public void setProviderMetadataLocation(@Nonnull @NotEmpty final String metadataLocation)
- throws URISyntaxException, IOException, ParseException {
-
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- Constraint.isNotNull(StringSupport.trimOrNull(metadataLocation),
- "OpenID Connect metadata location cannot be null or empty");
-
- final URI issuerURI = new URI(metadataLocation);
- final URL providerConfigurationURL = issuerURI.resolve(".well-known/openid-configuration").toURL();
- final InputStream stream = providerConfigurationURL.openStream();
- String providerInfo = null;
- try (java.util.Scanner s = new java.util.Scanner(stream)) {
- providerInfo = s.useDelimiter("\\A").hasNext() ? s.next() : "";
- }
- oIDCProviderMetadata = OIDCProviderMetadata.parse(providerInfo);
-
- }
-
- /**
- * Setter for OpenId Scope values. New ones are be added to the {@code OIDCScopeValue#OPENID} scope.
- *
- * @param oidcScopes OpenID Connect Scope values, can be <code>null</code> and will be ignored.
- */
- public void setScope(@Nullable final List<String> oidcScopes) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- //As the OPENID scope is always set. New scopes can be null.
- if (oidcScopes==null) {
- return;
- }
-
- for (final String oidcScope : oidcScopes) {
- switch (oidcScope.toUpperCase()) {
- case "ADDRESS":
- scope.add(OIDCScopeValue.ADDRESS);
- break;
- case "EMAIL":
- scope.add(OIDCScopeValue.EMAIL);
- break;
- case "OFFLINE_ACCESS":
- scope.add(OIDCScopeValue.OFFLINE_ACCESS);
- break;
- case "PHONE":
- scope.add(OIDCScopeValue.PHONE);
- break;
- case "PROFILE":
- scope.add(OIDCScopeValue.PROFILE);
- break;
- default:
- }
- }
-
- }
-
- /**
- * Setter for the OpenID Connect Prompt value.
- *
- * @param oidcPrompt OpenID Connect Prompt value.
- */
- public void setPrompt(@Nullable final String oidcPrompt) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- prompt = new Prompt(oidcPrompt);
- }
-
- /**
- * Setter for the OpenID Connect Authentication Context Class Reference (ACR) values.
- *
- * @param oidcAcrs OpenId ACR values
- */
- public void setAcr(@Nullable @NonnullElements final List<String> oidcAcrs) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- if (null != oidcAcrs) {
- for (final String oidcAcr : oidcAcrs) {
- final ACR acr = new ACR(oidcAcr);
-
- if (acrs == null) {
- acrs = new ArrayList<ACR>();
- }
- if (acr!=null) {
- acrs.add(acr);
- }
- }
- }
-
- }
-
- /**
- * Setter for the OpenID Connect Display value.
- *
- * @param oidcDisplay OpenID Connect Display value.
- */
- public void setDisplay(@Nullable final String oidcDisplay) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- if (null != display) {
- try {
- display = Display.parse(oidcDisplay);
- } catch (final ParseException e) {
- log.error("{} Could not set display value",getLogPrefix(), e);
- }
- }
-
- }
-
- /**
- * Set the lookup strategy for the {@link AttributeContext}.
- *
- * @param strategy lookup strategy
- */
- public void setAttributeContextLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
- ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- attributeContextLookupStrategy =
- Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
-
- }
-
- /**
- * Returns the first {@link StringAttributeValue} value of the {@link IdPAttribute} that has key
- * <code>name</code> from the {@link OpenIDConnectContext#getResolvedIdPAttributes()}.
- *
- * @param oidcCtx context to get attributes from.
- * @param name of the attribute to find.
- * @return attribute value if found, null otherwise.
- */
- @Nullable private String attributeToString(@Nonnull final OpenIDConnectContext oidcCtx,
- @Nullable final String name) {
-
- if (oidcCtx.getResolvedIdPAttributes() == null) {
- log.warn("Attribute context not available");
- return null;
- }
- final IdPAttribute attribute = oidcCtx.getResolvedIdPAttributes().get(name);
- if (attribute == null || attribute.getValues().size() == 0) {
- log.debug("attribute " + name + " not found or has no values");
- return null;
- }
- for (final IdPAttributeValue attrValue : attribute.getValues()) {
- if (attrValue instanceof StringAttributeValue) {
- // We set the value
- return attrValue.getDisplayValue();
- }
- }
- return null;
- }
-
- /**
- * Build the requested claims into an JSONObject, used by
- * {@link #getRequestObject(OpenIDConnectContext, State)}.
- *
- * @param oidcCtx context to extract request claims from.
- * @return id token as a JSON Object.
- */
- @Nonnull private JSONObject buildIDToken(@Nonnull final OpenIDConnectContext oidcCtx) {
-
- final JSONObject idToken = new JSONObject();
- for (final Map.Entry<String, String> entry : requestClaims.entrySet()) {
- final String value = entry.getValue();
- final String claim = entry.getKey();
- if (value == null) {
- // 1. null value
- log.debug("Setting claim " + claim + " to null");
- idToken.put(entry.getKey(), value);
- continue;
- }
- log.debug("locating attribute for " + value);
- final String attrValue = attributeToString(oidcCtx, value);
- if (attrValue != null) {
- // 2. attribute value
- log.debug("Setting claim " + claim + " to value " + attrValue);
- idToken.put(claim, attrValue);
- continue;
- }
- if ("essential".equals(value)) {
- // 3. essential value
- final JSONObject obj = new JSONObject();
- obj.put("essential", true);
- log.debug("Setting claim " + claim + " to value " + obj.toJSONString());
- idToken.put(claim, obj);
- continue;
- }
- // 4. string value
- log.debug("Setting claim " + claim + " to value " + value);
- idToken.put(claim, value);
- }
-
- return idToken;
- }
-
- /**
- *
- * Construct a JWT claims Request Object (see OpenID Connect core 1.0 section 6) iff claims
- * are requested i.e. <code>requestClaims</code> is not <code>null</code>.
- *
- * <p>If the signing key is present, adds also state, iat claims and then signs it.</p>
- *
- * <p>Must be called as a last step before constructing the request.</p>
- *
- *
- * @param oidcCtx context for accessing attributes.
- * @param state to be added to the request object.
- * @return the request object, or null if one is not constructed.
- *
- * @throws Exception if attribute context is not available or parsing/signing fails.
- */
- @Nullable private JWT getRequestObject(@Nonnull final OpenIDConnectContext oidcCtx, @Nonnull final State state)
- throws Exception {
-
-
- if (requestClaims == null || requestClaims.size() == 0) {
- return null;
- }
- final JSONObject request = new JSONObject();
- request.put("client_id", clientID.getValue());
- request.put("response_type", responseType.toString());
- if (signPrvKey != null) {
- request.put("iss", clientID.getValue());
- request.put("aud", oIDCProviderMetadata.getIssuer().getValue());
- // If we sign we add also iat and state.
- request.put("state", state.getValue());
- request.put("iat", TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
- }
- // Build the id token as instructed.
- final JSONObject idToken = buildIDToken(oidcCtx);
- final JSONObject claims = new JSONObject();
- claims.put("id_token", idToken);
- request.put("claims", claims);
- log.debug("Request object without signature "+getLogPrefix() + request.toJSONString());
- final JWTClaimsSet claimsRequest = JWTClaimsSet.parse(request);
- JWT requestObject = null;
- if (signPrvKey != null) {
- requestObject = new SignedJWT(new JWSHeader.Builder(jwsAlgorithm).keyID(keyID).build(), claimsRequest);
- ((SignedJWT) requestObject).sign(new RSASSASigner(signPrvKey));
- log.debug("created request object: " + requestObject.getParsedString());
- } else {
- requestObject = new PlainJWT(new PlainHeader(), claimsRequest);
- }
- return requestObject;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
-
- final OpenIDConnectContext oidcCtx =
- authenticationContext.getSubcontext(OpenIDConnectContext.class, true);
-
- // Initialize the context, if request is passive we override default prompt value
- final Prompt ovrPrompt = authenticationContext.isPassive() ? new Prompt(Type.NONE) : prompt;
- oidcCtx.setPrompt(ovrPrompt);
- oidcCtx.setAcrs(acrs);
- oidcCtx.setClientID(clientID);
- oidcCtx.setClientSecret(clientSecret);
- oidcCtx.setDisplay(display);
- oidcCtx.setoIDCProviderMetadata(oIDCProviderMetadata);
- oidcCtx.setRedirectURI(redirectURI);
- final State state = new State();
- oidcCtx.setState(state);
- final Nonce nonce = new Nonce();
- oidcCtx.setNonce(nonce);
-
- JWT requestObject = null;
- try {
- // must be called as a last step
- requestObject = getRequestObject(oidcCtx, state);
- } catch (final Exception e) {
- // TODO: better error id
- log.error("{} unable to create request object", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
- return;
- }
- if (authenticationContext.isForceAuthn()) {
- // We set max age to 0 if forcedauth is set
- // TODO: Currently the underlying library doesn't accept value 0, so
- // we set it to 1
- final int maxAge = 1;
- oidcCtx.setAuthenticationRequestURI(
- new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
- .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
- .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
- .maxAge(maxAge).prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
- } else {
- oidcCtx.setAuthenticationRequestURI(
- new AuthenticationRequest.Builder(responseType, scope, clientID, redirectURI)
- .endpointURI(oIDCProviderMetadata.getAuthorizationEndpointURI()).display(display)
- .acrValues(acrs).requestObject(requestObject).responseMode(ResponseMode.QUERY)
- .prompt(ovrPrompt).state(state).nonce(nonce).build().toURI());
- }
-
- return;
- }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
new file mode 100644
index 0000000..5edd244
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TokenResponseIDTokenLookupStrategy.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.TokenResponseContext;
+
+/** Return the base64 encoded id_token from the TokenResponseContext, or null if not found.*/
+public class TokenResponseIDTokenLookupStrategy implements Function<ProfileRequestContext, String> {
+
+ @Override
+ @Nullable public String apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final MessageContext inbound = profileRequestContext.getInboundMessageContext();
+ if (inbound == null) {
+ return null;
+ }
+ final TokenResponseContext tokenResponseContext = inbound.getSubcontext(TokenResponseContext.class);
+ if (tokenResponseContext == null) {
+ return null;
+ }
+ if (tokenResponseContext.getRawTokenResponse() != null) {
+ final Object idTokenObject = tokenResponseContext.getRawTokenResponse().get("id_token");
+ if (idTokenObject instanceof String) {
+ return (String) idTokenObject;
+ }
+ }
+ return null;
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.java
new file mode 100644
index 0000000..72e5cce
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateAuthenticationResponseResult.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+
+/**
+ * An action that checks the inbound authentication response indicated success, as opposed to an error.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds.INVALID_MSG_CTX}
+ * @event {@link EventIds.MESSAGE_PROC_ERROR}
+ */
+public class ValidateAuthenticationResponseResult extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateAuthenticationResponseResult.class);
+
+ /** Context containing the result to examine. */
+ @Nullable private AuthenticationResponse authenticationResponse;
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final MessageContext inMsgContext = profileRequestContext.getInboundMessageContext();
+ if (inMsgContext == null) {
+ log.debug("{} Inbound message context was null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ if (inMsgContext.getMessage() instanceof AuthenticationResponse) {
+ authenticationResponse = (AuthenticationResponse) inMsgContext.getMessage();
+ } else {
+ log.debug("{} Inbound message was not an authentication response", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ if (!authenticationResponse.indicatesSuccess()) {
+ final AuthenticationErrorResponse error = authenticationResponse.toErrorResponse();
+ log.error("{} OIDC Authentication Response contained an error from upstream OP '{}' : {}",
+ getLogPrefix(), authenticationContext.getAuthenticatingAuthority(), error.getErrorObject());
+ // TODO: maybe more specific error, and or an error branch in the flow e.g. maybe a UI element?
+ ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+ } else {
+ log.debug("{} Downstream OP signalled a successful response", getLogPrefix());
+ }
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java
new file mode 100644
index 0000000..fbee66d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateExternalAuthenticationContext.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An action that checks for an {@link ExternalAuthenticationContext} for a signaled event via the
+ * {@link ExternalAuthenticationContext#getAuthnError()} method.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @event various
+ */
+public class ValidateExternalAuthenticationContext extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateExternalAuthenticationContext.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.warn("{} OIDC Proxy authentication attempt signaled an error: {}", getLogPrefix(),
+ extContext.getAuthnError());
+ ActionSupport.buildEvent(profileRequestContext, extContext.getAuthnError());
+ } else {
+ log.trace("{} Continuing with OIDC Proxy authentication attempt", getLogPrefix());
+ }
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
deleted file mode 100644
index f64e36e..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenACR.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.text.ParseException;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.openid.connect.sdk.claims.ACR;
-
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- * An action that verifies the Authentication Context Class Reference (ACR) values contained within the
- * id_token matches those requested.
- *
- * <p>ACR claims are optional</p>
- *
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @event {@link net.shibboleth.idp.authn.AuthnEventIds#NO_CREDENTIALS}
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- *
- * @since 4.0.0
- */
-//TODO P.S should some of these functions be delegated to Nimbus IDTokenValidator?
-public class ValidateIDTokenACR extends AbstractAuthenticationAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenACR.class);
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
-
- final OpenIDConnectContext oidcCtx =
- authenticationContext.getSubcontext(OpenIDConnectContext.class);
- if (oidcCtx == null) {
- log.error("{} Unable to find OIDC context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
- return;
- }
-
-
- final List<ACR> acrs = oidcCtx.getAcrs();
- if (acrs != null && !acrs.isEmpty()) {
- if (log.isTraceEnabled()) {
- for (int i = 0; i < acrs.size(); i++) {
- log.trace("{} ACR index {} is {}", getLogPrefix(), i, acrs.get(i));
- }
- }
- final String acr;
- try {
- //TODO P.S. this could be null.
- acr = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("acr");
- } catch (final ParseException e) {
- log.error("{} Error parsing id token", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- if (StringSupport.trimOrNull(acr) == null) {
- log.error("{} acr requested but not received", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- if (!acrs.contains(new ACR(acr))) {
- log.error("{} acr received does not match requested:" + acr, getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- }
-
- return;
- }
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
deleted file mode 100644
index 1f2cbb3..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateIDTokenAuthorizedParty.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import java.text.ParseException;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An action that verifies the Authorized Party (azp) of an id_token. That is, the party to which
- * the id_token was issued, or the presenter of the id_token. As the IdP will always be responsible
- * for presenting the id_token, this value should always be the same client_id as the sole audience (aud).
- * As such, azp is not strictly required, but is checked if present.
- *
- * <p>The authorized party is one or more case sensitive Strings or URIs.</p>
- *
- * <p>See section 3.1.3.7 of the OpenID Connect core 1.0</p>
- *
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @pre <pre>OpenIDConnectContext.getIDToken() != null</pre>
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- *
- * @since 4.0.0
- */
-//Issues of semantics exist here e.g. https://bitbucket.org/openid/connect/issues/973/
-//TODO the code does not match the spec?
-public class ValidateIDTokenAuthorizedParty extends AbstractAuthenticationAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateIDTokenAuthorizedParty.class);
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
- final OpenIDConnectContext oidcCtx =
- authenticationContext.getSubcontext(OpenIDConnectContext.class);
- if (oidcCtx == null) {
- log.error("{} Unable to find oidc context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
-
- try {
- if (oidcCtx.getIDToken().getJWTClaimsSet().getAudience().size() > 1) {
- final String azp = oidcCtx.getIDToken().getJWTClaimsSet().getStringClaim("azp");
- if (!oidcCtx.getClientID().getValue().equals(azp)) {
- log.error("{} multiple audiences, client is not the azp", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- }
- } catch (final ParseException e) {
- log.error("{} Error parsing id token", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- return;
- }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
deleted file mode 100644
index 01c9603..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationResponse.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements. See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractExtractionAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OpenIDConnectContext;
-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;
-
-import com.nimbusds.oauth2.sdk.ParseException;
-import com.nimbusds.oauth2.sdk.id.State;
-import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
-import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
-import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
-import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-
-/**
- * An action that extracts the id_token from the query parameters of a successful response from
- * an OpenID Connect provider. The id_token is attached to the {@link OpenIDConnectContext}.
- *
- * <p>Also checks the state returned by the provider matches that which was sent to the provider</p>
- *
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null</pre>
- * @pre <pre>AuthenticationContext.getSubcontext(OpenIDConnectContext.class, false) != null</pre>
- * @pre <pre>OpenIdConnectContext.getAuthenticationResponseURI() != null</pre>
- * @post If {@link AuthenticationResponse#indicatesSuccess()} == true, the extracted id_token
- * is extracted and placed into the existing {@link OpenIDConnectContext}.
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- *
- * @since 4.0.0
- */
-public class ValidateOIDCAuthenticationResponse extends AbstractExtractionAction {
-
- /*
- * A DRAFT PROTO CLASS!! NOT TO BE USED YET.
- *
- * FINAL GOAL IS TO MOVE FROM CURRENT OIDC TO MORE WEBFLOW LIKE IMPLEMENTATION.
- */
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateOIDCAuthenticationResponse.class);
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nonnull final AuthenticationContext authenticationContext) {
-
-
- final OpenIDConnectContext oidcCtx =
- authenticationContext.getSubcontext(OpenIDConnectContext.class);
- if (oidcCtx == null) {
- log.info("{} Unable to find oidc context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
-
- if (oidcCtx.getAuthenticationResponseURI() == null) {
- log.info("{} Response URI not set", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- log.debug("Validating OIDC response {}", oidcCtx.getAuthenticationResponseURI().toString());
- AuthenticationResponse response = null;
- try {
- response = AuthenticationResponseParser.parse(oidcCtx.getAuthenticationResponseURI());
- } catch (final ParseException e) {
- log.info("{} Response parsing failed", getLogPrefix(),e);
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- if (!response.indicatesSuccess()) {
-
- final AuthenticationErrorResponse errorResponse = (AuthenticationErrorResponse) response;
- String error = errorResponse.getErrorObject().getCode();
- final String errorDescription = errorResponse.getErrorObject().getDescription();
- if (StringSupport.trimOrNull(errorDescription) != null) {
- error += " : " + errorDescription;
- }
-
- log.info("{} response indicated error: {}", getLogPrefix(), error);
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
- return;
- }
- final AuthenticationSuccessResponse successResponse = (AuthenticationSuccessResponse) response;
- // implicit and hybrid flows return id token in response.
- oidcCtx.setIDToken(successResponse.getIDToken());
- final State state = oidcCtx.getState();
- if (state == null || !state.equals(successResponse.getState())) {
- log.info("{} OIDC flow state mismatch:", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-
- }
-
- oidcCtx.setAuthenticationSuccessResponse(successResponse);
-
- return;
- }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java
new file mode 100644
index 0000000..1af7cf3
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateResponseState.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/**
+ * Authentication action that validates the authentication response state parameter
+ * (which is <b>required</b> in the Proxy flow) matches that in the authentication request.
+ *
+ * <p>Note, the state parameter could be an encoded format with more than one component. Even if so,
+ * the entire state value between request and response is matched. There is no need to decompose it.</p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ * FIXME: condiditions
+ * @pre <pre>
+ * ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null
+ * </pre>
+ *
+ * @pre <pre>
+ * AuthenticationContext.getSubcontext(DuoOIDCAuthenticationContext.class, false) != null
+ * </pre>
+ *
+ */
+public class ValidateResponseState extends AbstractOIDCAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(ValidateResponseState.class);
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ log.trace("{} Proxy authentication request state '{}' was returned in the response as '{}'",
+ getLogPrefix(),getAuthenticationRequest().getState(),getAuthenticationResponse().getState());
+
+ if (getAuthenticationRequest().getState() == null || getAuthenticationResponse().getState() == null) {
+ log.error("{} The state parameter was not present in either the request or response, "
+ + "state is mandatory for proxy OIDC requests",getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ if (!getAuthenticationRequest().getState().equals(getAuthenticationResponse().getState())) {
+ log.error("{} Request state did not match response state, has it been tampered with!",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+ // else state is fine.
+ log.debug("{} OIDC request and response state match, continuing",getLogPrefix());
+
+ }
+
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java
new file mode 100644
index 0000000..7ff6061
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultClientIDForIssuerLookupFunction.java
@@ -0,0 +1,59 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+
+
+/**
+ * Strategy to pull out the ID of the OIDC Client registered with to talk to the discovered/configured OP.
+ */
+public class DefaultClientIDForIssuerLookupFunction implements ContextDataLookupFunction<MessageContext, ClientID> {
+
+ /** A mapping between an OP Issuer and an RP Client.*/
+ @Nonnull @NonnullElements private final Map<Issuer, ClientID> issuerToClientMap;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param map the issuer to client lookup map.
+ */
+ public DefaultClientIDForIssuerLookupFunction(
+ @Nullable @ParameterName(name = "issuerToClientMap") final Map<String, String> map) {
+ if (map == null) {
+ issuerToClientMap = Collections.emptyMap();
+ } else {
+ issuerToClientMap = new HashMap<>(map.size());
+ for (Map.Entry<String, String> entry : map.entrySet()) {
+ issuerToClientMap.put(new Issuer(entry.getKey()), new ClientID(entry.getValue()));
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Nullable public ClientID apply(@Nullable final MessageContext input) {
+ if (input == null) {
+ return null;
+ }
+ final OIDCProviderMetadataContext provider = input.getSubcontext(OIDCProviderMetadataContext.class);
+ if (provider == null || provider.getProviderInformation() == null) {
+ return null;
+ }
+ return issuerToClientMap.get(provider.getProviderInformation().getIssuer());
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
index 8791c87..4e9bed0 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/DefaultIssuerIDLookupFunction.java
@@ -5,13 +5,12 @@ import javax.annotation.concurrent.ThreadSafe;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
-import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
/**
- * Strategy to pull out the ID of the OIDC proxy issuer/provider.
+ * Strategy to pull out the ID of the OIDC proxy issuer/provider from the {@link OIDCPeerEntityContext}.
*/
-//TODO is this where the OP ID should be? confusing
-//TODO should resolve a Nimbus issuer?
@ThreadSafe
public class DefaultIssuerIDLookupFunction implements ContextDataLookupFunction<MessageContext, String> {
@@ -21,10 +20,10 @@ public class DefaultIssuerIDLookupFunction implements ContextDataLookupFunction<
return null;
}
- final SAMLPeerEntityContext context = input.getSubcontext(SAMLPeerEntityContext.class);
+ final OIDCPeerEntityContext context = input.getSubcontext(OIDCPeerEntityContext.class);
if (context == null) {
return null;
}
- return context.getEntityId();
+ return context.getIdentifier();
}
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
similarity index 90%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
index aff7fdc..fcbccc7 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCMetadataLookupHandler.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCClientMetadataLookupHandler.java
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
-import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
import net.shibboleth.oidc.metadata.ClientInformationResolver;
import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -42,12 +42,12 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
/**
* Handler for inbound OIDC protocol messages that attempts to locate OIDC metadata for a RP, and attaches it with a
- * {@link OIDCMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
+ * {@link OIDCClientMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
*/
-public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
+public class OIDCClientMetadataLookupHandler extends AbstractMessageHandler {
/** Logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCMetadataLookupHandler.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(OIDCClientMetadataLookupHandler.class);
/** Resolver used to look up OIDC client information. */
@NonnullAfterInit private ClientInformationResolver clientResolver;
@@ -59,7 +59,7 @@ public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
/**
* Constructor.
*/
- public OIDCMetadataLookupHandler() {
+ public OIDCClientMetadataLookupHandler() {
issuerLookupStrategy = new DefaultIssuerIDLookupFunction();
}
@@ -116,13 +116,13 @@ public class OIDCMetadataLookupHandler extends AbstractMessageHandler {
return;
}
log.debug("{} Resolved client information for client '{}'", getLogPrefix(), clientInformation.getID());
- final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+ final OIDCClientMetadataContext oidcCtx = new OIDCClientMetadataContext();
oidcCtx.setClientInformation(clientInformation);
messageContext.addSubcontext(oidcCtx);
// Based on that info we know 1) client is valid 2) we know valid
// redirect uris
log.debug("{} {} added to MessageContext as child of {}", getLogPrefix(),
- OIDCMetadataContext.class.getName(), messageContext.getClass().getName());
+ OIDCClientMetadataContext.class.getName(), messageContext.getClass().getName());
} catch (final ResolverException e) {
log.error("{} ResolverException thrown during client information lookup", getLogPrefix(), e);
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
index bf413c2..bee1189 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/metadata/impl/OIDCProviderMetadataLookupHandler.java
@@ -4,7 +4,9 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
+import org.opensaml.messaging.context.BaseContext;
import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.messaging.handler.AbstractMessageHandler;
import org.opensaml.messaging.handler.MessageHandlerException;
import org.slf4j.Logger;
@@ -13,7 +15,10 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
@@ -26,10 +31,9 @@ import net.shibboleth.utilities.java.support.resolver.ResolverException;
/**
* Handler for inbound OIDC protocol messages that attempts to locate OIDC metadata for a OP (issuer), and attaches it with a
- * {@link OIDCMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
+ * {@link OIDCClientMetadataContext} as a child of a pre-existing instance of {@link MessageContext}.
*/
-//TODO should this be conflated with the OIDCMetadataLookupHandler service
-//TODO is commons the correct place to put this?
+//TODO This might need a way to set which base class to add the metadata too.
public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
/** Logger. */
@@ -37,11 +41,26 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
/** Resolver used to look up OIDC provider information. */
@NonnullAfterInit private ProviderMetadataResolver providerResolver;
-
+
/** Strategy used to obtain the issuer id value for the inbound message context. */
- @NonnullAfterInit private Function<MessageContext,String> issuerIDLookupStrategy;
+ @NonnullAfterInit private Function<MessageContext,String> issuerIDLookupStrategy;
+
+ /** Strategy to resolve the context class to add the resolved metadata too.*/
+ @Nonnull private Function<MessageContext,? extends BaseContext> contextClassLookupStrategy;
+
+ /** Constructor.*/
+ public OIDCProviderMetadataLookupHandler() {
+ contextClassLookupStrategy = new ChildContextLookup<>(OIDCPeerEntityContext.class);
+ }
+
+ public void setContextClassLookupStrategy(
+ @Nonnull final Function<MessageContext, ? extends BaseContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ contextClassLookupStrategy =
+ Constraint.isNotNull(strategy, "Context class lookup strategy can not be null");
+ }
-
/**
* Set the strategy used to locate the client id of the request.
*
@@ -49,10 +68,12 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
*/
public void setIssuerIDLookupStrategy(@Nonnull final Function<MessageContext, String> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
issuerIDLookupStrategy =
Constraint.isNotNull(strategy, "IssuerIDLookupStrategy lookup strategy cannot be null");
}
+
/**
* Set the {@link ProviderMetadataResolver} to use.
*
@@ -60,6 +81,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
*/
public void setProviderMetadataResolver(@Nonnull final ProviderMetadataResolver resolver) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
providerResolver = Constraint.isNotNull(resolver, "IssuerMetadataResolver cannot be null");
}
@@ -81,10 +103,20 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
protected void doInvoke(final MessageContext messageContext) throws MessageHandlerException {
ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ final BaseContext entityCtx = contextClassLookupStrategy.apply(messageContext);
+
+ if (entityCtx == null) {
+ log.info("{} OIDC entity context class '{}' missing", getLogPrefix(),
+ OIDCPeerEntityContext.class);
+ return;
+ }
+
// Resolve issuer id from inbound message
- //TODO should resolve an Issuer directly here?
final String issuerId = issuerIDLookupStrategy.apply(messageContext);
-
+ if (issuerId == null) {
+ log.warn("{} No issuer returned from lookup strategy", getLogPrefix());
+ return;
+ }
final IssuerIDCriterion issuerCriterion = new IssuerIDCriterion(new Issuer(issuerId));
final CriteriaSet criteria = new CriteriaSet(issuerCriterion);
try {
@@ -94,7 +126,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
return;
}
log.debug("{} Found provider metadata for '{}'", getLogPrefix(), issuerId);
- messageContext.addSubcontext(new OIDCProviderMetadataContext(issuerMetadata));
+ entityCtx.addSubcontext(new OIDCProviderMetadataContext(issuerMetadata));
} catch (final ResolverException e) {
log.error("{} ResolverException thrown during provider metadata lookup", getLogPrefix(), e);
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
index 4cb940c..11a5944 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-beans.xml
@@ -58,10 +58,10 @@
p:fieldExtractors="#{getObject('shibboleth.FlowStartAuditExtractors') ?: getObject('shibboleth.DefaultFlowStartAuditExtractors')}" />
- <bean id="PrepareInboundMessageContext"
- class="net.shibboleth.idp.saml.session.impl.PrepareInboundMessageContext" scope="prototype"
+ <bean id="PrepareOIDCInboundMessageContext"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PrepareOIDCInboundMessageContext" scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext">
- <property name="relyingPartyLookupStrategy">
+ <property name="identiferLookupStrategy">
<bean parent="shibboleth.Functions.Compose" c:f-ref="ParentAuthenticiationContextLookup">
<constructor-arg name="g">
<bean parent="shibboleth.Functions.Expression" c:expression="#input.getAuthenticatingAuthority()" />
@@ -87,7 +87,7 @@
<bean id="OIDCClientMetadataLookup" parent="NestedWebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="INBOUND">
<constructor-arg name="messageHandler">
- <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCMetadataLookupHandler" scope="prototype">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCClientMetadataLookupHandler" scope="prototype">
<property name="clientInformationResolver">
<ref bean="shibboleth.oidc.rp.ClientInformationResolver" />
</property>
@@ -104,8 +104,7 @@
<bean id="InitializeRelyingPartyContext"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeRelyingPartyContext" scope="prototype"
- p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
- p:issuerIDLookupStrategy-ref="shibboleth.oidc.rp.IssuerIDLookupStrategy" />
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
<bean id="InitializeOutboundMessageContext"
@@ -122,9 +121,14 @@
class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext" />
+ <bean id="PopulateResponseTypeAndModeContext" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.PopulateResponseTypeAndModeContext"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+
<bean id="AddAuthzRequest" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddAuthzRequest"
- p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+
<!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
<bean id="messageEncoderFactory"
@@ -133,22 +137,22 @@
<!-- List must itself be a prototype so new encoders are created per request -->
<util:list id="shibboleth.authn.oidc.rp.AuthenticationRequestEncoders" scope="prototype">
- <ref bean="OIDCAuthnRedirectRequestEncoder" />
- <ref bean="OIDCAuthnPostRequestEncoder" />
+ <ref bean="HTTPRedirectAuthnRequestEncoder" />
+ <ref bean="HTTPPostAuthnRequestEncoder" />
</util:list>
- <bean id="OIDCAuthnRedirectRequestEncoder"
- class="net.shibboleth.oidc.profile.encoder.impl.HTTPRedirectAuthnEncoder" init-method="" scope="prototype"
+ <bean id="HTTPRedirectAuthnRequestEncoder"
+ class="net.shibboleth.oidc.profile.encoder.impl.HTTPRedirectAuthnRequestEncoder" init-method="" scope="prototype"
p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
- <bean id="OIDCAuthnPostRequestEncoder" class="net.shibboleth.oidc.profile.encoder.impl.HTTPPostAuthnEncoder"
+ <bean id="HTTPPostAuthnRequestEncoder" class="net.shibboleth.oidc.profile.encoder.impl.HTTPPostAuthnRequestEncoder"
init-method="" scope="prototype" p:velocityEngine-ref="shibboleth.VelocityEngine"
p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
<bean id="EncodeMessage" class="org.opensaml.profile.action.impl.EncodeMessage" scope="prototype"
- p:messageEncoderFactory-ref="messageEncoderFactory" p:httpServletResponse-ref="shibboleth.HttpServletResponse" />
-
+ p:messageEncoderFactory-ref="messageEncoderFactory" p:httpServletResponse-ref="shibboleth.HttpServletResponse"/>
+
<!-- TODO: Place holder for message handlers -->
<bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain" scope="prototype">
<property name="handlers">
@@ -158,9 +162,97 @@
</property>
</bean>
+ <!-- Message Decoding -->
+ <bean id="messageDecoderFactory" class="net.shibboleth.idp.saml.profile.impl.SpringAwareMessageDecoderFactory">
+ <property name="beanMappings">
+ <map>
+ <entry key="query" value="OIDCRedirectAuthnResponseDecoder" />
+ <entry key="form_post" value="OIDCPostAuthnResponseDecoder" />
+ </map>
+ </property>
+ </bean>
+
+ <bean id="OIDCRedirectAuthnResponseDecoder"
+ class="net.shibboleth.oidc.profile.decoding.impl.HTTPRedirectAuthnResponseDecoder" init-method="" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+
+ <bean id="OIDCPostAuthnResponseDecoder"
+ class="net.shibboleth.oidc.profile.decoding.impl.HTTPPostAuthnResponseDecoder" init-method="" scope="prototype"
+ p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
+ <!-- After authentication response -->
+
+ <bean id="ValidateExternalAuthenticationContext" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateExternalAuthenticationContext" />
+
+ <bean id="ValidateAuthenticationResponseResult" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateAuthenticationResponseResult"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+
+ <bean id="ValidateResponseStateMatchesRequest" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateResponseState"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"/>
+
+
+ <!-- CODE flow beans -->
+
+ <bean id="ExchangeCodeForToken" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExchangeCodeForToken"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:authenticationContextLookupStrategy-ref="ParentAuthenticiationContextLookup"
+ p:httpClient="#{getObject('shibboleth.authn.oidc.rp.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.authn.oidc.rp.HttpClientSecurityParameters')}"
+ p:tokenResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder')}"
+ p:tokenRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder')}"/>
+
+ <bean id="ExtractIDTokenFromTokenResponse" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExtractIDTokenFromResponse"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
+ p:rawIdTokenLookupStrategy-ref="TokenResponseIDTokenLookupStrategy"/>
+
+ <bean id="TokenResponseIDTokenLookupStrategy"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.TokenResponseIDTokenLookupStrategy"/>
+
+ <!-- could these be singletons? -->
+ <bean id="shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultTokenResponseDecoder"
+ p:objectMapper-ref="shibboleth.authn.oidc.rp.JSONObjectMapper"/>
+
+ <bean id="shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.DefaultTokenRequestEncoder"/>
+
+ <!-- Create a default object mapper. Setup should not change once injected -->
+ <bean id="shibboleth.authn.oidc.rp.JSONObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+ p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="setSerializationInclusion">
+ <property name="arguments">
+ <util:constant static-field="com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL" />
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+ p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="registerModule">
+ <property name="arguments">
+ <bean class="com.fasterxml.jackson.datatype.jsr310.JavaTimeModule" />
+ </property>
+ </bean>
+
+ <bean class="org.springframework.beans.factory.config.MethodInvokingBean"
+ p:targetObject-ref="shibboleth.authn.oidc.rp.JSONObjectMapper" p:targetMethod="setDateFormat">
+ <property name="arguments">
+ <bean class="java.text.SimpleDateFormat" c:_0="yyyy-MM-dd'T'HH:mm:ss.SSSZZ" />
+ </property>
+ </bean>
+
+
+ <!-- Process token -->
+
+
<!-- OLD STUFF -->
@@ -188,17 +280,11 @@
<property name="responseType" value="id_token token"/>
</bean> -->
- <bean id="ValidateOIDCAuthenticationResponse"
- class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthenticationResponse"
- p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
-
<bean id="ValidateIDTokenSignature"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenSignature" />
<bean id="ValidateOIDCAuthentication" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOIDCAuthentication" scope="prototype" />
- <bean id="GetOIDCTokenResponse" class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.GetOIDCTokenResponse"
- p:httpServletRequest-ref="shibboleth.HttpServletRequest" />
<bean id="ValidateTokenClaims" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateTokenClaims"
@@ -266,7 +352,7 @@
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.OIDCAuthenticationTimeRequested"/>
<bean id="shibboleth.authn.oidc.rp.jwt.DefaultNonceActivationCondition"
- class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActiviationCondition"/>
+ class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActivationCondition"/>
<bean id="shibboleth.authn.oidc.rp.jwt.DefaultIssuerLookupStrategy"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.OIDCIssuerClaimLookupStrategy"/>
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
index 1935724..ebcf8e6 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/OIDCRelyingParty/oidc-relying-party-authn-flow.xml
@@ -20,13 +20,9 @@
<!-- Nest new PRC inside AC -->
<evaluate expression="InitializeProxyProfileRequestContext" />
<evaluate expression="FlowStartPopulateAuditContext" />
+ <!-- Init inbound msg context and create OP Peer -->
+ <evaluate expression="PrepareOIDCInboundMessageContext" />
- <!-- this is nice to get the rp out of the authenticating auth and setup a msg context
- but do we need it for a SAMLPeerEntityContext? -->
- <evaluate expression="PrepareInboundMessageContext" />
-
- <!-- <evaluate expression="SAMLProtocolAndRole" /> maybe we need an OIDC role selector here, to say this is an
- OP over the normal RP -->
<evaluate expression="OIDCProviderMetadataLookup" />
<evaluate expression="InitializeRelyingPartyContext" />
<evaluate expression="SelectRelyingPartyConfiguration" />
@@ -36,14 +32,13 @@
<!-- <evaluate expression="PostLookupPopulateAuditContext" /> -->
<evaluate expression="InitializeOutboundMessageContext" />
<evaluate expression="SelectProfileConfiguration" />
+ <evaluate expression="PopulateResponseTypeAndModeContext"/>
<evaluate expression="AddAuthzRequest"/>
<!-- <evaluate expression="PostRequestPopulateAuditContext" />
<evaluate expression="WriteAuditLog" /> -->
<!--
- <evaluate expression="InitializeMessageChannelSecurityContext" />
- <evaluate expression="PopulateBindingAndEndpointContexts" />
- <evaluate expression="PopulateRequestSignatureSigningParameters" />
+ <evaluate expression="InitializeMessageChannelSecurityContext" />
-->
<evaluate expression="'proceed'" />
@@ -56,30 +51,80 @@
<on-render>
<evaluate
expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(false)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" />
- <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext(EncodeMessage), true)" result="flowScope.oidcContext" />
+ <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext(EncodeMessage, messageDecoderFactory), true)" result="flowScope.oidcContext" />
<evaluate expression="flowScope.oidcContext.setOutboundMessageHandler(PreEncodeMessageHandler)" />
</on-render>
<transition to="ValidateResponse" />
</view-state>
<action-state id="ValidateResponse">
- <evaluate expression="ValidateOIDCAuthenticationResponse" />
+ <evaluate expression="ValidateExternalAuthenticationContext"/>
+ <evaluate expression="ValidateAuthenticationResponseResult"/>
+ <evaluate expression="ValidateResponseStateMatchesRequest"/>
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="SwitchOnOIDCFlow" />
+ </action-state>
+
+ <!-- Switch flow path based on OIDC response_type/flow used.
+ TODO possible places for an NPE?-->
+ <decision-state id="SwitchOnOIDCFlow">
+ <!-- check a null response_type first, should never get here -->
+ <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+ .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType() == null"
+ then="UnsupportedFlow" />
+ <!-- Check for CODE flow -->
+ <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+ .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesCodeFlow()"
+ then="CodeFlow" />
+ <!-- Check for Hybrid flow -->
+ <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+ .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesImplicitFlow()"
+ then="HybridFlow" />
+ <!-- Check for IMPLICIT flow -->
+ <!-- final IF has an else if an unsupported flow is used (should not happen) -->
+ <if test="opensamlProfileRequestContext.getSubcontext('net.shibboleth.idp.authn.context.AuthenticationContext')
+ .getSubcontext('org.opensaml.profile.context.ProfileRequestContext').getOutboundMessageContext().getSubcontext('net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext').getResponseType().impliesHybridFlow()"
+ then="ImplicitFlow" else="UnsupportedFlow" />
+ </decision-state>
+
+ <action-state id="CodeFlow">
+ <evaluate expression="ExchangeCodeForToken"/>
+ <evaluate expression="ExtractIDTokenFromTokenResponse"/>
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="ValidateToken" />
+ </action-state>
+
+ <action-state id="HybridFlow">
+
<evaluate expression="'proceed'" />
- <transition on="proceed" to="GetToken" />
+ <transition on="proceed" to="ValidateToken" />
</action-state>
+
+ <action-state id="ImplicitFlow">
+
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="ValidateToken" />
+ </action-state>
+
+ <action-state id="UnsupportedFlow">
- <action-state id="GetToken">
- <evaluate expression="GetOIDCTokenResponse" />
<evaluate expression="'proceed'" />
- <transition on="proceed" to="ValidateOIDCTokenResponse" />
+ <transition on="proceed" to="ValidateToken" />
+ </action-state>
+
+ <action-state id="ValidateToken">
+
+
+ <transition on="proceed" to="SetPrincipal" />
</action-state>
- <action-state id="ValidateOIDCTokenResponse">
+ <!-- <action-state id="ValidateOIDCTokenResponse">
<evaluate expression="ValidateIDTokenSignature" />
<evaluate expression="ValidateTokenClaims" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="SetPrincipal" />
- </action-state>
+ </action-state> -->
<action-state id="SetPrincipal">
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
new file mode 100644
index 0000000..57d01e1
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultTokenResponseDecoderTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.mockito.Mockito;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Tests for the DefaultTokenResponseDecoder.*/
+public class DefaultTokenResponseDecoderTest extends AbstractOIDCTest {
+
+ /** The encoder to test.*/
+ private DefaultTokenResponseDecoder decoder;
+
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ decoder = new DefaultTokenResponseDecoder();
+ decoder.setObjectMapper(new ObjectMapper());
+ }
+
+
+ @Test
+ public void testEncoder_Success() throws ComponentInitializationException, UnsupportedOperationException, IOException {
+ decoder.initialize();
+ final HttpResponse response = Mockito.mock(HttpResponse.class);
+ final HttpEntity mockHttpEntity = Mockito.mock(HttpEntity.class);
+ final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+ when(mockStatusLine.getStatusCode()).thenReturn(200);
+ when(response.getStatusLine()).thenReturn(mockStatusLine);
+ when(response.getEntity()).thenReturn(mockHttpEntity);
+ when(mockHttpEntity.getContent()).thenReturn(new ByteArrayInputStream(ID_TOKEN_RESPONSE.getBytes()));
+ final Map<String, Object> decodedResponse = decoder.apply(response);
+ assertNotNull(decodedResponse);
+ assertNotNull(decodedResponse.get("id_token"));
+ assertNotNull(decodedResponse.get("access_token"));
+ assertNotNull(decodedResponse.get("token_type"));
+ assertNotNull(decodedResponse.get("scope"));
+ }
+
+ @Test
+ public void testEncoder_NullResponseFields() throws ComponentInitializationException {
+ decoder.initialize();
+ final HttpResponse response = Mockito.mock(HttpResponse.class);
+
+ final Map<String, Object> decodedResponse = decoder.apply(response);
+ assertNull(decodedResponse);
+
+ }
+
+ @Test
+ public void testEncoder_InternalServerErrorResponse() throws ComponentInitializationException {
+ decoder.initialize();
+ final HttpResponse response = Mockito.mock(HttpResponse.class);
+ final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+ when(mockStatusLine.getStatusCode()).thenReturn(500);
+ when(response.getStatusLine()).thenReturn(mockStatusLine);
+
+ final Map<String, Object> decodedResponse = decoder.apply(response);
+ assertNull(decodedResponse);
+
+ }
+
+ @Test
+ public void testEncoder_NoMessageEntity() throws ComponentInitializationException {
+ decoder.initialize();
+ final HttpResponse response = Mockito.mock(HttpResponse.class);
+ final StatusLine mockStatusLine = Mockito.mock(StatusLine.class);
+ when(mockStatusLine.getStatusCode()).thenReturn(200);
+ when(response.getStatusLine()).thenReturn(mockStatusLine);
+ when(response.getEntity()).thenReturn(null);
+
+ final Map<String, Object> decodedResponse = decoder.apply(response);
+ assertNull(decodedResponse);
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
new file mode 100644
index 0000000..b0b68e8
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
+
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertNotNull;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+
+/** Tests for the DefaultTokenRequestEncoder.*/
+public class DefaultTokenRequestEncoderTest extends AbstractOIDCTest {
+
+ /** The encoder to test.*/
+ private DefaultTokenRequestEncoder encoder;
+
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ encoder = new DefaultTokenRequestEncoder();
+ }
+
+
+ @Test
+ public void testEncoder_Success() throws Exception {
+ encoder.initialize();
+ final HttpUriRequest request = encoder.apply(prc);
+ assertNotNull(request);
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ assertNotNull(request.getFirstHeader("Authorization"));
+ assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+ String content = new String(
+ ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+ assertTrue("grant_type expected in request", content.contains("grant_type"));
+ assertTrue("authorization_code expected in request", content.contains("authorization_code"));
+ assertTrue("code expected in request", content.contains("code"));
+ assertTrue("redirect_uri expected in request", content.contains("redirect_uri"));
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
new file mode 100644
index 0000000..0c183e7
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+
+/** Abstract class for tests that require context setup appropriate for an OIDC request/response.*/
+public abstract class AbstractOIDCTest {
+
+ protected final String GOOD_PROVIDER_CONFIGURATION_INFO =
+ "{\"issuer\":\"https://op.example.com/\","
+ + "\"authorization_endpoint\":\"https://www.certification.openid.net/test/a/"
+ + "test_rp_proxy/authorize\",\"token_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/token\","
+ + "\"jwks_uri\":\"https://www.certification.openid.net/test/a/test_rp_proxy/jwks\","
+ + "\"userinfo_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/userinfo\","
+ + "\"registration_endpoint\":\"https://www.certification.openid.net/test/a/test_rp_proxy/register\","
+ + "\"scopes_supported\":[\"openid\",\"phone\",\"profile\",\"email\",\"address\",\"offline_access\"],"
+ + "\"response_types_supported\":[\"code\",\"id_token code\",\"token code id_token\",\"id_token\","
+ + "\"token id_token\",\"token code\",\"token\"],\"response_modes_supported\":[\"query\",\"fragment\","
+ + "\"form_post\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"],"
+ + "\"token_endpoint_auth_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\","
+ + "\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+ + "\"grant_types_supported\":[\"authorization_code\",\"implicit\"],\"claims_parameter_supported\":true,"
+ + "\"acr_values_supported\":[\"PASSWORD\"],\"subject_types_supported\":[\"public\",\"pairwise\"],"
+ + "\"claim_types_supported\":[\"normal\",\"aggregated\",\"distributed\"],"
+ + "\"claims_supported\":[\"sub\",\"name\",\"given_name\",\"family_name\","
+ + "\"middle_name\",\"nickname\",\"gender\",\"birthdate\",\"preferred_username\",\"profile\","
+ + "\"website\",\"locale\",\"updated_at\",\"address\",\"zoneinfo\",\"phone_number\",\"phone_number_verified\","
+ + "\"email\",\"email_verified\"],\"id_token_signing_alg_values_supported\":[\"none\",\"RS256\",\"RS384\","
+ + "\"RS512\",\"PS256\",\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+ + "\"id_token_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"ECDH-ES\","
+ + "\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\",\"A128GCMKW\","
+ + "\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"id_token_encryption_enc_values_supported\":[\"A128CBC-HS256\","
+ + "\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"],"
+ + "\"request_object_signing_alg_values_supported\":[\"none\",\"RS256\",\"RS384\",\"RS512\",\"PS256\","
+ + "\"PS384\",\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+ + "\"request_object_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\","
+ + "\"ECDH-ES\",\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\","
+ + "\"A128GCMKW\",\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"request_object_encryption_enc_values_supported\":"
+ + "[\"A128CBC-HS256\",\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"],"
+ + "\"userinfo_signing_alg_values_supported\":[\"RS256\",\"RS384\",\"RS512\",\"PS256\",\"PS384\","
+ + "\"PS512\",\"ES256\",\"ES256K\",\"ES384\",\"ES512\",\"EdDSA\"],"
+ + "\"userinfo_encryption_alg_values_supported\":[\"RSA1_5\",\"RSA-OAEP\",\"RSA-OAEP-256\",\"ECDH-ES\","
+ + "\"ECDH-ES+A128KW\",\"ECDH-ES+A192KW\",\"ECDH-ES+A256KW\",\"A128KW\",\"A192KW\",\"A256KW\",\"A128GCMKW\","
+ + "\"A192GCMKW\",\"A256GCMKW\",\"dir\"],\"userinfo_encryption_enc_values_supported\":[\"A128CBC-HS256\","
+ + "\"A192CBC-HS384\",\"A256CBC-HS512\",\"A128GCM\",\"A192GCM\",\"A256GCM\"]}";
+
+ /** Real response from openid connect tests,*/
+ @Nonnull @NotEmpty
+ protected final String ID_TOKEN_RESPONSE = "{\n"
+ + " \"access_token\": \"W0y5aDNAzEPNpSzu1cuMG904BZuQFZJUUwG5F3ct0zydZWy1ji\",\n"
+ + " \"token_type\": \"Bearer\",\n"
+ + " \"id_token\": \"eyJraWQiOiJiNjliYzcyOS05NDJjLTQzNjItYmM2YS03OWU3MjAwOWY0Yzg"
+ + "iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiM0pBZmFibUx4eWVnNjJJM2JiT0RrdyIsInN1YiI"
+ + "6InVzZXItc3ViamVjdC0xMjM0NTMxIiwiYXVkIjoibXl0ZXN0Y2xpZW50IiwiaXNzIjoiaHR0cHM6"
+ + "XC9cL3d3dy5jZXJ0aWZpY2F0aW9uLm9wZW5pZC5uZXRcL3Rlc3RcL2FcL3Rlc3RfcnBfcHJveHlcLy"
+ + "IsImV4cCI6MTY0MzI3NTQyNCwiaWF0IjoxNjQzMjc1MTI0fQ.igU798iSZYUR7Z6_MQQendFCqm9Pf"
+ + "6Xpd4KtnHcPGG5FxtXPiDhlyhP_7ewSIQtzWxQ5wEatixDJWl2oRasivNCJyl6ZNeUG4YuT8S7xlgp"
+ + "UumoycBxrmomS7wgZSola8oZo8BuiRKlWzqLLtFkAChGi5iJEtHfVBe3k3PyQAsWRQdtixkvdxvyLq"
+ + "FLznIaPmMH-OZ82w7sJf4bpalZSAaR5NLpyzLFw-r81IOtOVrpQtnycCw0w_JxAP16GyZXCJc56vCE"
+ + "o8rji7juk3szTOclbsYFrrd9iokWHl0qJa0G1ev5XQLsiUxYxgO1Nw-CAnHtESZ9w7JIfLLX1YlY73g\",\n"
+ + " \"scope\": \"openid\"\n"
+ + "}";
+
+ /** Client metadata.*/
+ @Nonnull protected final String CLIENT_METADATA = "[\n"
+ + " {\n"
+ + " \"issuer\": \"https://op.example.com\",\n"
+ + " \"scope\": \"openid info profile email address phone\",\n"
+ + " \"redirect_uris\": [\n"
+ + " \"https://192.168.0.150/static\"\n"
+ + " ],\n"
+ + " \"client_id\": \"https://rp.example.com\",\n"
+ + " \"client_secret\": \"secret\",\n"
+ + " \"response_types\": [\n"
+ + " \"id_token\",\n"
+ + " \"id_token token\"\n"
+ + " ]\n"
+ + " }"
+ + "]";
+
+ /** The root profile request context to use.*/
+ protected ProfileRequestContext rootPrc;
+
+ /** The profile request context to use.*/
+ protected ProfileRequestContext prc;
+
+ /** The request context to use.*/
+ protected RequestContext src;
+
+ /**
+ * Setup the various contexts.
+ *
+ * @throws Exception on error
+ */
+ public void setup() throws Exception {
+
+ src = new RequestContextBuilder().buildRequestContext();
+ rootPrc = new WebflowRequestContextProfileRequestContextLookup().apply(src);
+
+ final AuthenticationContext ac = new AuthenticationContext();
+ ac.setAuthenticatingAuthority("https://op.example.com");
+ rootPrc.addSubcontext(ac);
+ // Add a nested proxy PRC under the authentication context.
+ prc = new ProfileRequestContext();
+ ac.addSubcontext(prc);
+
+ final MessageContext outMsgCtx = new MessageContext();
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://rp.example.com"));
+ request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+ outMsgCtx.setMessage(request);
+ prc.setOutboundMessageContext(outMsgCtx);
+
+ final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+ final OIDCClientMetadataContext metadataContext = new OIDCClientMetadataContext();
+ metadataContext.setClientInformation(
+ OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
+ outMsgCtx.addSubcontext(metadataContext);
+
+ final OIDCPeerEntityContext peerEntitCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
+ final OIDCProviderMetadataContext providerCtx =
+ new OIDCProviderMetadataContext(
+ OIDCProviderMetadata.parse((JSONObject)parser.parse(GOOD_PROVIDER_CONFIGURATION_INFO)));
+ peerEntitCtx.addSubcontext(providerCtx);
+ outMsgCtx.addSubcontext(peerEntitCtx);
+
+ //Set the inbound reponse.
+
+ final MessageContext inMsgCtx = new MessageContext();
+ inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+ new URI("/idp/profile/Authn/OIDC/RP/callback"
+ + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX")));
+
+ prc.setInboundMessageContext(inMsgCtx);
+ prc.setOutboundMessageContext(outMsgCtx);
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
index 676c62c..8cf421c 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationControllerTest.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -18,9 +35,10 @@ import javax.servlet.http.HttpServletResponse;
import org.mockito.Mockito;
import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+import org.opensaml.messaging.decoder.servlet.AbstractHttpServletRequestMessageDecoder;
import org.opensaml.messaging.encoder.MessageEncoder;
import org.opensaml.messaging.encoder.MessageEncodingException;
-import org.opensaml.messaging.handler.MessageHandlerException;
import org.opensaml.profile.action.AbstractProfileAction;
import org.opensaml.profile.action.ActionSupport;
import org.opensaml.profile.action.EventIds;
@@ -60,9 +78,8 @@ import net.shibboleth.idp.session.context.SessionContext;
import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration.OIDCHttpRequestMethod;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
-import net.shibboleth.oidc.profile.encoder.OIDCMessageEncoder;
+import net.shibboleth.oidc.profile.decoding.OIDCMessageDecoder;
import net.shibboleth.oidc.profile.encoder.impl.AbstractOIDCMessageEncoder;
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.net.HttpServletSupport;
import net.shibboleth.utilities.java.support.net.URLBuilder;
@@ -134,6 +151,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
assertTrue(result.getResponse().getRedirectedUrl().contains("scope"));
}
+ //TODO the callback method
+
/**
* Export the FlowExecutor to the servlet context with the correct set of configured contexts. Mimicking the
* IdP's configuration of the {@link ServletContextAttributeExporter}.
@@ -184,7 +203,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
encoder.setHttpServletResponse(response);
final MockEncodeMessage encode = new MockEncodeMessage(encoder);
encode.setHttpServletResponse(response);
- final OIDCAuthnContext oidcContext = new OIDCAuthnContext(encode);
+ final OIDCAuthnContext oidcContext = new OIDCAuthnContext(encode,
+ decoder -> new NoOpMockRedirectResponseDecoder());
ac.addSubcontext(oidcContext);
@@ -273,6 +293,17 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
}
}
+ private class NoOpMockRedirectResponseDecoder
+ extends AbstractHttpServletRequestMessageDecoder implements OIDCMessageDecoder {
+
+ @Override
+ protected void doDecode() throws MessageDecodingException {
+ //do nothing
+
+ }
+
+ }
+
private class MockRedirectEncoder extends AbstractOIDCMessageEncoder {
@Override
@@ -302,9 +333,6 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
}
}
-
-
-
}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java
new file mode 100644
index 0000000..21c54ed
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ExchangeCodeForTokenTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.context.navigate.ParentContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.util.StandardCharset;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+
+/**
+ * Tests for the {@link ExchangeCodeForToken} action.
+ */
+public class ExchangeCodeForTokenTest extends AbstractOIDCTest {
+
+ /** The action to test.*/
+ @Nonnull private ExchangeCodeForToken exchangeAction;
+
+
+ @BeforeMethod
+ public void setup() throws Exception {
+
+ super.setup();
+
+ exchangeAction = new ExchangeCodeForToken();
+ exchangeAction.setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
+ exchangeAction.setProfileContextLookupStrategy(new ChildContextLookup<>(ProfileRequestContext.class).compose(
+ new ChildContextLookup<>(AuthenticationContext.class)
+ .compose(new WebflowRequestContextProfileRequestContextLookup())));
+ }
+
+ /**
+ * Test the token exchange works using a mocked HTTP client and response.
+ * Does not check the signature is valid. Given the amount of mocking, is not
+ * a very thorough test.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testTokenExchange() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+ final StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+ Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+
+ Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+ Mockito.when(httpResponse.getEntity()).thenReturn(new StringEntity(ID_TOKEN_RESPONSE));
+ Mockito.when(httpClient.execute((HttpUriRequest) Mockito.any(), (HttpContext) Mockito.any()))
+ .thenReturn(httpResponse);
+
+ // create new client with mock response
+ exchangeAction.setHttpClient(httpClient);
+
+ exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/token")
+ .build();
+ } catch (URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ exchangeAction.setTokenResponseDecoderStrategy(response -> {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ return mapper.readValue(
+ httpResponse.getEntity().getContent(), new TypeReference<Map<String, Object>>() {});
+ } catch (UnsupportedOperationException | IOException e) {
+ return null;
+ }
+ });
+
+ exchangeAction.initialize();
+
+ final Event event = exchangeAction.execute(src);
+ //Null is success
+ assertNull(event);
+
+ }
+
+ @Test
+ public void testNullTokenResponse() throws Exception {
+
+ final HttpClient httpClient = Mockito.mock(HttpClient.class);
+ final HttpResponse httpResponse = Mockito.mock(HttpResponse.class);
+ final StatusLine statusLine = Mockito.mock(StatusLine.class);
+ Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine);
+ Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+
+ // create new client with mock response
+ exchangeAction.setHttpClient(httpClient);
+
+ exchangeAction.setTokenRequestEncoderStrategy(prc -> {
+ URI uri;
+ try {
+ uri = new URIBuilder().setScheme("https")
+ .setHost("op.example.com")
+ .setPath("/token")
+ .build();
+ } catch (URISyntaxException e) {
+ return null;
+ }
+
+ // Add headers and create request.
+ final RequestBuilder rb = RequestBuilder.post().setUri(uri)
+ .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+ .setCharset(StandardCharset.UTF_8);
+ return rb.build();
+ });
+ exchangeAction.setTokenResponseDecoderStrategy(response -> null);
+
+ exchangeAction.initialize();
+
+ final Event event = exchangeAction.execute(src);
+ //Null is success
+ assertNotNull(event);
+ assertEquals("AuthenticationException",event.getId());
+
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index ee88be0..9a7c66a 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -1,3 +1,20 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import static org.mockito.ArgumentMatchers.any;
@@ -5,6 +22,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
+import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -16,6 +34,7 @@ import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.protocol.HttpContext;
import org.junit.Test;
+import org.opensaml.messaging.context.MessageContext;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -23,15 +42,33 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.engine.Flow;
+import org.springframework.webflow.engine.impl.FlowExecutionImpl;
import org.springframework.webflow.execution.FlowExecution;
import org.springframework.webflow.test.MockFlowBuilderContext;
import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.minidev.json.parser.JSONParser;
import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
+import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCClientMetadataContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.messaging.context.OIDCProviderMetadataContext;
import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTests;
import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
@@ -98,6 +135,21 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+ "]\n"
+ "}";
+ @Nonnull private final String CLIENT_METADATA = "[\n"
+ + " {\n"
+ + " \"issuer\": \"https://op.example.com\",\n"
+ + " \"scope\": \"openid info profile email address phone\",\n"
+ + " \"redirect_uris\": [\n"
+ + " \"https://192.168.0.150/static\"\n"
+ + " ],\n"
+ + " \"client_id\": \"demo_rp\",\n"
+ + " \"response_types\": [\n"
+ + " \"id_token\",\n"
+ + " \"id_token token\"\n"
+ + " ]\n"
+ + " }"
+ + "]";
+
/** Path to the flow to be tested.*/
@Nonnull private static final String FLOW =
@@ -181,7 +233,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
}
@Test
- public void testFlowToRedirect() {
+ public void testFlowToAuthorizationRedirect() {
setFlowPath(FLOW);
setFlowModelResources(flowResources);
setSubflows(subflows);
@@ -208,4 +260,153 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
flowExecution.start(inputMap, externalContext);
assertCurrentStateEquals("AuthRequest");
}
+
+ /**
+ * Test the flow from the external authorization request to the end of the flow.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testAuthnFlowFromAuthorizationCallback() throws Exception {
+
+ setFlowPath(FLOW);
+ setFlowModelResources(flowResources);
+ setSubflows(subflows);
+
+ final Map<String,String> mockProperties = Map.of(
+ "idp.service.clientinfo.failFast","false",
+ "idp.oidc.rp.clientID","clientId",
+ "idp.oidc.rp.clientSecret","secret",
+ "idp.oidc.rp.providerConfigurationDocument","provider_location",
+ "idp.oidc.rp.redirectURI","https://localhost:8443/idp/profile/Authn/OIDC/RP/callback",
+ "idp.oidc.rp.scope","email",
+ "idp.entityID", "http://idp.example.com/",
+ "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
+
+ setMockProperties(mockProperties);
+
+ final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+ .createFlowExecution(getFlowDefinition());
+ final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
+ prc.getSubcontext(AuthenticationContext.class).setAuthenticatingAuthority("http://op.example.com");
+
+ // create a nested PRC under the authentication context
+ final ProfileRequestContext nestPrc = (ProfileRequestContext)
+ prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);
+
+ final MessageContext outMsgCtx = new MessageContext();
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+ request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+ outMsgCtx.setMessage(request);
+
+ final OIDCPeerEntityContext peerCtx = new OIDCPeerEntityContext();
+ OIDCProviderMetadata providerMetadata = OIDCProviderMetadata.parse(GOOD_PROVIDER_CONFIGURATION_INFO);
+ final OIDCProviderMetadataContext providerMetadataCtx = new OIDCProviderMetadataContext(providerMetadata);
+ peerCtx.addSubcontext(providerMetadataCtx);
+
+ outMsgCtx.addSubcontext(peerCtx);
+ nestPrc.setOutboundMessageContext(outMsgCtx);
+
+ final ResponseTypeAndModeContext respCtx = new ResponseTypeAndModeContext();
+ respCtx.setResponseMode(ResponseMode.QUERY);
+ respCtx.setResponseType(ResponseType.CODE);
+ nestPrc.getOutboundMessageContext().addSubcontext(respCtx);
+
+ final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
+ final OIDCClientMetadataContext metadataContext = new OIDCClientMetadataContext();
+ metadataContext.setClientInformation(
+ OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
+ nestPrc.getOutboundMessageContext().addSubcontext(metadataContext);
+
+ final MessageContext inMsgCtx = new MessageContext();
+ inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+ new URI("/idp/profile/Authn/OIDC/RP/callback"
+ + "?state=8df98fd63a53fa5b5433d6f8754bca5d.65317332&code=z8C2DCp6sn0D9aGbEqlrFesdPVRXPtDX")));
+ nestPrc.setInboundMessageContext(inMsgCtx);
+
+ prc.getSubcontext(AuthenticationContext.class)
+ .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+ flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+
+
+ updateFlowExecution(flowExecution);
+
+ //set start view and ending event to transition on.
+ externalContext.setEventId("proceed");
+ setCurrentState("AuthRequest");
+ resumeFlow(externalContext);
+
+ //assert success conditions
+ assertFlowExecutionEnded();
+ assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+
+
+ }
+
+
+ /**
+ * Test the flow from the external authorization request to the end of the flow when an error
+ * is returned from the downstream OP.
+ *
+ * @throws Exception on error.
+ */
+ @Test
+ public void testAuthnFlowFromAuthorizationCallback_ErrorAuthenticationResponse() throws Exception {
+
+ setFlowPath(FLOW);
+ setFlowModelResources(flowResources);
+ setSubflows(subflows);
+
+ final Map<String,String> mockProperties = Map.of(
+ "idp.service.clientinfo.failFast","false",
+ "idp.oidc.rp.clientID","clientId",
+ "idp.oidc.rp.clientSecret","secret",
+ "idp.oidc.rp.providerConfigurationDocument","provider_location",
+ "idp.oidc.rp.redirectURI","https://localhost:8443/idp/profile/Authn/OIDC/RP/callback",
+ "idp.oidc.rp.scope","email",
+ "idp.entityID", "http://idp.example.com/",
+ "idp.authn.oidc.rp.proxyIssuer","https://op.example.com");
+
+ setMockProperties(mockProperties);
+
+ final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+ .createFlowExecution(getFlowDefinition());
+ final ProfileRequestContext prc = buildProfileRequestContext("authn/OIDCRelyingParty", false,false);
+
+ prc.getSubcontext(AuthenticationContext.class)
+ .addSubcontext(new ExternalAuthenticationContext(new ExternalAuthenticationImpl(false)));
+ flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+
+ // create a nested PRC under the authentication context
+ final ProfileRequestContext nestPrc = (ProfileRequestContext)
+ prc.getSubcontext(AuthenticationContext.class).addSubcontext(new ProfileRequestContext(), true);
+
+ final MessageContext outMsgCtx = new MessageContext();
+ final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://op.example.com"));
+ request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+ outMsgCtx.setMessage(request);
+ nestPrc.setOutboundMessageContext(outMsgCtx);
+
+ final MessageContext inMsgCtx = new MessageContext();
+ inMsgCtx.setMessage(AuthenticationResponseParser.parse(
+ new URI("/idp/profile/Authn/OIDC/RP/callback?"
+ + "error=login_required&error_description=Login%20required&"
+ + "state=d0c455126e9078aaf5a8e84c0e1910ad.65317332")));
+ nestPrc.setInboundMessageContext(inMsgCtx);
+
+
+ updateFlowExecution(flowExecution);
+
+ //set start view and ending event to transition on.
+ externalContext.setEventId("proceed");
+ setCurrentState("AuthRequest");
+ resumeFlow(externalContext);
+
+ //assert success conditions
+ assertFlowExecutionEnded();
+ //TODO FIX SUCCESS CONDITIONS
+ assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+
+
+ }
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list