[java-idp-plugin-oidc-rp] branch main updated: Add client_id and client_authentication resolvers. Various additions
Phil Smart
philip.smart at jisc.ac.uk
Fri Mar 18 11:21:49 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=71753712ba037a555ee0ccb96d2c3005740f34a8
The following commit(s) were added to refs/heads/main by this push:
new 7175371 Add client_id and client_authentication resolvers. Various additions
7175371 is described below
commit 71753712ba037a555ee0ccb96d2c3005740f34a8
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Mar 18 11:21:43 2022 +0000
Add client_id and client_authentication resolvers. Various additions
- Improve Token Request encoder. Using some parts of the Nimbus
TokenRequest API. Supports client_basic_secret and post. JWT type would
not be too difficult to wire up, not sure of private_key_jwt.
- Add reloadable client_authentication and client_id resolver services.
Although this should be part of the profile/sec config rather than
inside an action.
- Add redirect URI from request URI generation. Using allowed hosts,
and an override from the profile config
- Remove client information metadata usage
- Tests
---
idp-oidc-rp-api/pom.xml | 7 +-
.../ClientIDFromOAuth2ClientContextFunction.java | 71 +++++++
.../config/navigate/RedirectUriLookupFunction.java | 54 +++++
.../authn/oidc/rp/context/OAuth2ClientContext.java | 76 +++++++
.../rp/storage/ClientAuthenticationDetails.java | 70 +++++++
.../impl/AbstractRequestEncoderFunction.java | 74 ++++---
...ava => DefaultAuthCodeTokenRequestEncoder.java} | 21 +-
.../impl/DefaultUserInfoRequestEncoder.java | 31 ++-
.../impl/NimbusAuthCodeTokenRequestEncoder.java | 94 +++++++++
.../AbstractOIDCAuthenticationResponseAction.java | 20 +-
...uest.java => AddOIDCAuthenticationRequest.java} | 67 +++----
.../oidc/rp/impl/AuthorizationController.java | 112 ++++++++++-
.../impl/DefaultRedirectUriCreationFunction.java | 222 +++++++++++++++++++++
.../rp/impl/DefaultResponseTypeLookupFunction.java | 1 +
...nitializeOAuth2ClientAuthenticationContext.java | 197 ++++++++++++++++++
.../rp/impl/InitializeOAuth2ClientContext.java | 185 +++++++++++++++++
...OutboundAuthorizationRequestMessageContext.java | 18 +-
.../MapBackedMemoryStorageServiceFactoryBean.java | 119 +++++++++++
.../impl/OAuth2ClientAuthenticationContainer.java | 147 ++++++++++++++
...lientAuthenticationResolverServiceStrategy.java | 65 ++++++
.../impl/OAuth2ClientIdentifierContainer.java | 148 ++++++++++++++
...th2ClientIdentifierResolverServiceStrategy.java | 67 +++++++
...eloadingOAuth2ClientAuthenticationProvider.java | 107 ++++++++++
.../ReloadingOAuth2ClientIdentifierProvider.java | 105 ++++++++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 127 ++++++++++--
.../oidc-relying-party-authn-beans.xml | 86 ++++----
.../oidc-relying-party-authn-flow.xml | 6 +-
.../authn/clientauthentication-resolver-system.xml | 33 +++
.../authn/clientidentifier-resolver-system.xml | 30 +++
.../authn/providermetadata-resolver-system.xml | 20 +-
...=> DefaultAuthCodeTokenRequestEncoderTest.java} | 6 +-
.../NimbusAuthCodeTokenRequestEncoderTest.java | 169 ++++++++++++++++
.../authn/oidc/rp/impl/AbstractOIDCTest.java | 12 +-
.../oidc/rp/impl/AuthorizationControllerTest.java | 23 ++-
.../DefaultRedirectUriCreationFunctionTest.java | 169 ++++++++++++++++
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 71 +++----
.../oidc-client-registration-authentication.xml | 28 +++
.../authn/oidc-client-registration-clientid.xml | 21 ++
.../conf/authn/oidc-providermetadata-resolvers.xml | 2 +-
pom.xml | 13 --
40 files changed, 2613 insertions(+), 281 deletions(-)
diff --git a/idp-oidc-rp-api/pom.xml b/idp-oidc-rp-api/pom.xml
index 2a8126f..ace1fa8 100644
--- a/idp-oidc-rp-api/pom.xml
+++ b/idp-oidc-rp-api/pom.xml
@@ -50,7 +50,12 @@
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<scope>provided</scope>
- </dependency>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java
new file mode 100644
index 0000000..4edcb4d
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ClientIDFromOAuth2ClientContextFunction.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A function that pulls the client_id out of the {@link OAuth2ClientContext}.
+ */
+//TODO could be moved to commons
+ at ThreadSafe
+public class ClientIDFromOAuth2ClientContextFunction
+ implements BiFunction<ProfileRequestContext, JWTClaimsSet, String> {
+
+ /** Strategy that will return a @link OAuth2ClientContext}. */
+ @Nonnull
+ private final Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param strgy the strategy used to locate the {@link OAuth2ClientContext}
+ */
+ public ClientIDFromOAuth2ClientContextFunction(
+ @Nonnull @ParameterName(name="oauth2ClientContextLookupStrategy")
+ final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+ oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public String apply(@Nonnull final ProfileRequestContext prc, @Nullable final JWTClaimsSet claimsSet) {
+ final OAuth2ClientContext clientContext = oauth2ClientContextLookupStrategy.apply(prc);
+ if (clientContext == null) {
+ return null;
+ }
+ return clientContext.getClientId();
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.java
new file mode 100644
index 0000000..fdeece6
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/RedirectUriLookupFunction.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.config.navigate;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.profile.config.ProfileConfiguration;
+import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
+
+/**
+ * Pull a redirect_uri from the Relying Party profile config if present. Return {@literal null} otherwise.
+ */
+public class RedirectUriLookupFunction extends AbstractRelyingPartyLookupFunction<URI> {
+
+ @Override
+ @Nullable public URI apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDCAuthorizationConfiguration) {
+ try {
+ return new URI(((OIDCAuthorizationConfiguration) pc).getRedirectUriOverride(input));
+ } catch (final URISyntaxException e) {
+ return null;
+ }
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java
new file mode 100644
index 0000000..a491bbf
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/context/OAuth2ClientContext.java
@@ -0,0 +1,76 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.context;
+
+import java.net.URI;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A context to store information pertaining to the OAuth2 client (Relying Party) to use in communication
+ * with a OpenId Connect Provider.
+ *
+ * <p>Typically a subcontext under {@link OIDCPeerEntityContext}, as it relates to the client
+ * associated pairwise with the downstream OP peer.</p>
+ *
+ * @parent {@link OIDCPeerEntityContext}
+ * @added During an OAuth 2.0 authentication request attempt
+ */
+public class OAuth2ClientContext extends BaseContext {
+
+ /** The client_id.*/
+ @Nullable private String clientId;
+
+
+ /** An redirect URI that should take preference over any automatically computed.*/
+ @Nullable private URI redirectUriOverride;
+
+
+ /**
+ * Set the redirect_uri to use in place of any other.
+ *
+ * @param override the redirect_uri
+ *
+ * @return this
+ */
+ public OAuth2ClientContext setRedirectUriOverride(@Nullable final URI override) {
+ redirectUriOverride = override;
+ return this;
+ }
+
+ /**
+ * Get the redirect_uri which should be used in place of any other.
+ *
+ * @return the redirect_uri
+ */
+ public URI getRedirectUriOverride() {
+ return redirectUriOverride;
+ }
+
+ /**
+ * Set the client_id.
+ *
+ * @param id the client_id
+ *
+ * @return this
+ */
+ public OAuth2ClientContext setClientId(@Nonnull @NotEmpty final String id) {
+ clientId = Constraint.isNotEmpty(id, "ClientID can not be null or empty");
+ return this;
+ }
+
+ /**
+ * Get the client_id.
+ *
+ * @return the client_id
+ */
+ public String getClientId() {
+ return clientId;
+ }
+
+
+}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java
new file mode 100644
index 0000000..b8cbdd4
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/storage/ClientAuthenticationDetails.java
@@ -0,0 +1,70 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.storage;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** A bean to hold client_authentication registration details.*/
+ at Immutable
+public class ClientAuthenticationDetails {
+
+ /** The client secret. */
+ @JsonProperty("client_secret") @Nonnull @NotEmpty private final char[] clientSecret;
+
+ /** When the client secret expires.*/
+ @JsonProperty("client_secret_expires_at") private final long clientSecretExpiresAt;
+
+ /** The token endpoint authentication method. */
+ @JsonProperty("token_endpoint_auth_method") @Nonnull @NotEmpty private final String tokenEndpointAuthMethod;
+
+ /**
+ * Constructor.
+ *
+ * @param secret The client secret
+ * @param secretExpiresAt When the client secret expires
+ * @param authMethod
+ */
+ public ClientAuthenticationDetails(@ParameterName(name="clientSecret") @Nonnull final char[] secret,
+ @ParameterName(name="clientSecretExpiresAt") final long secretExpiresAt,
+ @ParameterName(name="tokenEndpointAuthMethod") @Nonnull @NotEmpty final String authMethod) {
+ super();
+ clientSecret = Constraint.isNotNull(secret, "Secret can not be null");
+ clientSecretExpiresAt = secretExpiresAt;
+ tokenEndpointAuthMethod = Constraint.isNotEmpty(authMethod, "Authentication method can not be null or empty");
+ }
+
+ /**
+ * Get the client_secret.
+ *
+ * @return Returns the clientSecret.
+ */
+ @Nonnull public char[] getClientSecret() {
+ return clientSecret;
+ }
+
+ /**
+ * Get when the client secret expires in number of seconds since UNIX EPOCH.
+ *
+ * @return Returns the clientSecretExpiresAt.
+ */
+ public long getClientSecretExpiresAt() {
+ return clientSecretExpiresAt;
+ }
+
+ /**
+ * Get the token authentication method.
+ *
+ * @return Returns the tokenEndpointAuthMethod.
+ */
+ @Nonnull @NotEmpty public String getTokenEndpointAuthMethod() {
+ return tokenEndpointAuthMethod;
+ }
+
+
+
+}
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
index 30fd576..d1045b5 100644
--- 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
@@ -32,10 +32,12 @@ import org.slf4j.LoggerFactory;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -46,59 +48,69 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
implements Function<ProfileRequestContext, HttpUriRequest> {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(AbstractRequestEncoderFunction.class);
+ @Nonnull private final 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,OIDCMetadataContext> clientMetadataLookupStrategy;
+ /** The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext}.*/
+ @Nonnull
+ private Function<ProfileRequestContext, OAuth2ClientAuthenticationContext>
+ oauth2ClientAuthenticationContextLookupStrategy;
/** 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 OIDCMetadataContext clientMetadataContext;
/** OIDC Metadata context. */
@Nullable private OIDCProviderMetadataContext providerMetadataContext;
+ /**
+ * The context used to store client authentication information for communication with a
+ * downstream OP.
+ */
+ @Nullable private OAuth2ClientAuthenticationContext clientAuthnContext;
+
/** Constructor.*/
protected AbstractRequestEncoderFunction() {
providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
new OutboundMessageContextLookup()));
- clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class).compose(
- new OutboundMessageContextLookup());
+ oauth2ClientAuthenticationContextLookupStrategy =
+ new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
}
/**
- * Set the lookup strategy to locate the OpenID providers metadata.
+ * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext}
+ * from the {@link ProfileRequestContext}.
*
- * @param strategy the strategy.
+ * @param strgy the strategy.
*/
- public void setProviderMetadataLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+ public void setOIDCClientAuthenticationContextContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
- providerMetadataLookupStrategy =
- Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
}
/**
- * Set the lookup strategy to locate the OpenID client metadata.
+ * Set the lookup strategy to locate the OpenID providers metadata.
*
* @param strategy the strategy.
*/
- public void setClientMetadataLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> strategy) {
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
- clientMetadataLookupStrategy = Constraint.isNotNull(strategy,"Client metadata lookup strategy can not be null");
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
}
/**
@@ -120,12 +132,12 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
}
/**
- * Returns the client metadata context.
+ * Get the client authentication context.
*
- * @return The client metadata context.
+ * @return the client context.
*/
- @Nullable public OIDCMetadataContext getClientMetadataContext() {
- return clientMetadataContext;
+ @Nullable public OAuth2ClientAuthenticationContext getClientAuthenticationContext() {
+ return clientAuthnContext;
}
/**
@@ -140,7 +152,7 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
//TODO these come from the outbound message context, so are not general to other actions
@Override
- public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
+ @Nullable public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
final MessageContext inboundMessageCtx = profileRequestContext.getInboundMessageContext();
@@ -170,18 +182,18 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
}
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;
}
+ clientAuthnContext = oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
+ if (clientAuthnContext == null) {
+ log.error("No OAuth 2.0 client authentication context found");
+ return null;
+ }
+
return doApply(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/DefaultAuthCodeTokenRequestEncoder.java
similarity index 86%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoder.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
index 8d4b0cd..25d5120 100644
--- 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/DefaultAuthCodeTokenRequestEncoder.java
@@ -37,23 +37,24 @@ 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. */
+/** A token request encoder that builds an OAuth2.0 Access Token Request for an authorization code grant request. */
//TODO either only applies to authorization_code grant, or can handle more. If specific, must gurantee that.
-public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
+//TODO just remove in favour of the Nimbus version?
+public class DefaultAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
/** The HTTPS scheme.*/
@Nonnull @NotEmpty private static final String HTTPS = "https";
/** Class logger. */
@Nonnull
- private final Logger log = LoggerFactory.getLogger(DefaultTokenRequestEncoder.class);
+ private final Logger log = LoggerFactory.getLogger(DefaultAuthCodeTokenRequestEncoder.class);
@Override
@Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
try {
- if (getClientMetadataContext().getClientInformation() == null) {
- log.warn("No client information present to base token request off");
+ if (getClientAuthenticationContext() == null) {
+ log.warn("No client authentication context to base token request off");
return null;
}
// Mandate HTTPS, so construct the URL from that.
@@ -80,7 +81,7 @@ public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
buildSecretBasicAuthentication(rb);
final HttpUriRequest request = rb.build();
- log.debug("Token URL '{}'",request);
+ log.debug("Token URL '{}'",request);
return request;
} catch (final Exception e) {
@@ -119,15 +120,17 @@ public class DefaultTokenRequestEncoder extends AbstractRequestEncoderFunction {
*
* @param rb the request builder to use.
*/
- //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this.
+ //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this. Use the authn method here?
private void buildSecretBasicAuthentication(final RequestBuilder rb) {
final StringBuilder sb = new StringBuilder();
sb.append(URLEncoder.encode(
- getClientMetadataContext().getClientInformation().getID().getValue(), StandardCharsets.UTF_8));
+ getClientAuthenticationContext().getClientAuthentication()
+ .getClientID().getValue(), StandardCharsets.UTF_8));
sb.append(':');
sb.append(URLEncoder.encode(
- getClientMetadataContext().getClientInformation().getSecret().getValue(), StandardCharsets.UTF_8));
+ getClientAuthenticationContext().
+ getClientAuthentication().getMethod().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/DefaultUserInfoRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
index 3c7499f..d0fdb01 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.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.encoding.impl;
import java.net.URI;
@@ -35,7 +52,7 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
private final Logger log = LoggerFactory.getLogger(DefaultUserInfoRequestEncoder.class);
/** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
- @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext>
+ @Nonnull private final Function<ProfileRequestContext, AccessTokenResponseContext>
tokenResponseContextLookupStrategy;
@@ -50,11 +67,9 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
try {
- if (getClientMetadataContext().getClientInformation() == null) {
- log.warn("No client information present to base token request off");
- return null;
- }
- final AccessTokenResponseContext responseCtx = tokenResponseContextLookupStrategy.apply(profileRequestContext);
+
+ final AccessTokenResponseContext responseCtx =
+ tokenResponseContextLookupStrategy.apply(profileRequestContext);
if (responseCtx == null) {
log.debug("No TokenResponseContext returned by lookup strategy");
return null;
@@ -99,8 +114,8 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
*
* @throws OIDCRPException if there is an issue adding the bearer token to the Authorization header.
*/
- private void addBearerToken(@Nonnull final RequestBuilder rb, @Nonnull final AccessTokenResponseContext responseCtx)
- throws OIDCRPException {
+ private void addBearerToken(@Nonnull final RequestBuilder rb,
+ @Nonnull final AccessTokenResponseContext responseCtx) throws OIDCRPException {
// Double check is bearer scheme
if ("Bearer".equals(responseCtx.getRawTokenResponse().get("token_type"))
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
new file mode 100644
index 0000000..6f65a3d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.methods.RequestBuilder;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jose.util.StandardCharset;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+
+/**
+ * A token request encoder that builds an OAuth2.0 Access Token Request for an authorization_code grant
+ * using the Nimbus token request builders. The Nimbus {@link HTTPRequest} is converted to the
+ * returned {@link HttpUriRequest}.
+ * */
+public class NimbusAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(NimbusAuthCodeTokenRequestEncoder.class);
+
+ @Override
+ @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ try {
+ if (getClientAuthenticationContext() == null) {
+ log.warn("No client authentication context to base token request off");
+ return null;
+ }
+ final AuthorizationGrant codeGrant =
+ new AuthorizationCodeGrant(getAuthenticationResponse().getAuthorizationCode(),
+ getAuthenticationRequest().getRedirectURI());
+
+ final TokenRequest tokenRequest =
+ new TokenRequest(getProviderMetadataContext().getProviderInformation().getTokenEndpointURI(),
+ getClientAuthenticationContext().getClientAuthentication(), codeGrant);
+
+ return convertHttpRequest(tokenRequest.toHTTPRequest());
+
+ } catch (final Exception e) {
+ log.warn("Unable to encode token request", e);
+ }
+ return null;
+ }
+
+ /**
+ * Convert the internally used Nimbus {@link HTTPRequest} to the externally presented {@link HttpUriRequest}.
+ *
+ * @param request the HTTP request to convert
+ *
+ * @return the convert HTTP request
+ */
+ private HttpUriRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
+
+ if (request.getMethod() != HTTPRequest.Method.POST) {
+ // Should never happen as Nimbus should always use POST
+ log.warn("Token Request must use the HTTP POST method, is trying to use '{}'", request.getMethod());
+ return null;
+ }
+ final RequestBuilder rb = RequestBuilder.post().setUri(request.getURI()).setHeader(
+ "Content-Type", request.getEntityContentType().toString())
+ .setCharset(StandardCharset.UTF_8);
+
+ request.getQueryParameters().forEach((k,v) -> v.stream().forEach(value -> rb.addParameter(k, value)));
+ if (request.getAuthorization() != null && !request.getAuthorization().isEmpty()) {
+ rb.addHeader("Authorization", request.getAuthorization());
+ }
+ return rb.build();
+ }
+}
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
index 011597b..b301d15 100644
--- 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
@@ -55,7 +55,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAction {
/** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractOIDCAuthenticationAction.class);
/** Lookup strategy to locate the OP metadata to use.*/
@Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
@@ -68,9 +68,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
/** OIDC authentication request built by the IdP. */
@Nullable private OIDCAuthenticationRequest authnRequest;
-
- /** OIDC client Metadata context. */
- @Nullable private OIDCMetadataContext clientMetadataContext;
/** OIDC Metadata context. */
@Nullable private OIDCProviderMetadataContext providerMetadataContext;
@@ -128,14 +125,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
return authnRequest;
}
- /**
- * Returns the client metadata context.
- *
- * @return The client metadata context.
- */
- @Nullable public OIDCMetadataContext getClientMetadataContext() {
- return clientMetadataContext;
- }
/**
* Returns the OIDC provider metadata context.
@@ -185,13 +174,6 @@ abstract class AbstractOIDCAuthenticationAction extends AbstractAuthenticationAc
}
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());
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/AddOIDCAuthenticationRequest.java
similarity index 83%
rename from idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddAuthzRequest.java
rename to idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AddOIDCAuthenticationRequest.java
index 0665f4b..9ed4ba5 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/AddOIDCAuthenticationRequest.java
@@ -17,7 +17,6 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
-import java.util.List;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -34,15 +33,16 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.Nonce;
import net.shibboleth.idp.authn.AbstractAuthenticationAction;
import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.ResponseTypeAndModeContext;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
@@ -63,37 +63,38 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
*
* @post ProfileRequestContext.getOutboundMessageContext().getMessage() != null
*/
-public class AddAuthzRequest extends AbstractAuthenticationAction {
+public class AddOIDCAuthenticationRequest extends AbstractAuthenticationAction {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(AddAuthzRequest.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AddOIDCAuthenticationRequest.class);
/** Overwrite an existing message? */
private boolean overwriteExisting;
/** Applicable profile configuration. */
@Nullable private OIDCAuthorizationConfiguration profileConfiguration;
-
- /** The metadata belonging to the client registered with the current OpenID Provider.*/
- @Nullable private OIDCMetadataContext clientMetadata;
-
+
/** The metadata belonging to the OpenID Provider.*/
@Nullable private OIDCProviderMetadataContext providerMetadata;
/** The response type and mode context.*/
@Nullable private ResponseTypeAndModeContext responseTypeAndModeContext;
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+ @Nonnull
+ private final Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+ /** The stashed OAuth2 client context.*/
+ @Nullable private OAuth2ClientContext oauth2ClientContext;
+
/** 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,OIDCMetadataContext> clientMetadataLookupStrategy;
-
/** Lookup strategy to locate the response_mode and response_type context.*/
@Nonnull private Function<ProfileRequestContext,ResponseTypeAndModeContext> responseTypeAndModeLookupStrategy;
/** Constructor.*/
- public AddAuthzRequest() {
+ public AddOIDCAuthenticationRequest() {
// Fool the parent class into looking above instead of below the PRC for the context.
setAuthenticationContextLookupStrategy(new ParentContextLookup<>(AuthenticationContext.class));
@@ -101,11 +102,13 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
new InboundMessageContextLookup()));
- clientMetadataLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class).compose(
- new InboundMessageContextLookup());
-
responseTypeAndModeLookupStrategy = new ChildContextLookup<>(ResponseTypeAndModeContext.class).compose(
new OutboundMessageContextLookup());
+
+ // Default under OIDCPeerEntityContext in the outbound context (create true).
+ oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
}
/**
@@ -121,17 +124,6 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
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, OIDCMetadataContext> 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.
@@ -195,13 +187,14 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
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);
+
+ oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+ if (oauth2ClientContext == null) {
+ log.error("{} OAuth2 client context not found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
return false;
}
+
providerMetadata = providerMetadataLookupStrategy.apply(profileRequestContext);
if (providerMetadata == null) {
log.debug("{} Inbound message context did not contain provider metadata", getLogPrefix());
@@ -223,18 +216,17 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
final OIDCAuthenticationRequest request =
- new OIDCAuthenticationRequest(clientMetadata.getClientInformation().getID());
+ new OIDCAuthenticationRequest(new ClientID(oauth2ClientContext.getClientId()));
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());
// Add scopes
- final List<String> scopes =
- clientMetadata.getClientInformation().getMetadata().getScope().toStringList();
- scopes.forEach(s -> request.getScope().add(s));
+// final List<String> scopes =
+// clientMetadata.getClientInformation().getMetadata().getScope().toStringList();
+// scopes.forEach(s -> request.getScope().add(s));
//TODO use strategy with injectable secure random implementation?
request.setNonce(new Nonce(OIDCProxySupport.generateNonce(16)));
@@ -246,7 +238,8 @@ public class AddAuthzRequest extends AbstractAuthenticationAction {
// log.error("{} Unable to set prompt", e);
// }
- log.debug("{} Built authorization request for endpoint '{}'",getLogPrefix(), request.getEndpointURI());
+ log.debug("{} Built authorization request for endpoint '{}' for client '{}'",getLogPrefix(),
+ request.getEndpointURI(), oauth2ClientContext.getClientId());
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 c3d8b43..15a609c 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AuthorizationController.java
@@ -18,6 +18,8 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
import java.io.IOException;
+import java.net.URI;
+import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -35,6 +37,7 @@ 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.profile.context.navigate.OutboundMessageContextLookup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
@@ -47,11 +50,17 @@ 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.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
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.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
/**
*
@@ -76,8 +85,11 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
*/
@ThreadSafe
@Controller
- at RequestMapping("%{shibboleth.authn.OIDC.externalAuthnPath:/Authn/OIDC/RP}")
-public class AuthorizationController {
+ at RequestMapping("%{idp.authn.oidc.rp.externalAuthnPath:/Authn/OIDC/RP}")
+public class AuthorizationController extends AbstractInitializableComponent {
+
+ /** The final part of the IdP URL path which is called to begin authorization.*/
+ @Nonnull @NotEmpty public static final String AUTHORIZE_PATH_SEGMENT = "/authz";
/** The name of the Http parameter that stores the authorisation code.*/
@Nonnull @NotEmpty public static final String CODE_PARAMETER = "code";
@@ -89,11 +101,17 @@ public class AuthorizationController {
@Nonnull private final Logger log = LoggerFactory.getLogger(AuthorizationController.class);
/** Lookup strategy to locate the nested ProfileRequestContext. */
- @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextLookupStrategy;
+ @Nonnull private Function<ProfileRequestContext,ProfileRequestContext> profileRequestContextLookupStrategy;
+
+ /** Function to create a suitable redirect URI from the given servlet request and profile request context.*/
+ @NonnullAfterInit private BiFunction<HttpServletRequest, ProfileRequestContext, URI> redirectUriCreationStrategy;
/** Lookup strategy to locate the SAML context. */
@Nonnull private Function<ProfileRequestContext,OIDCAuthnContext> oidcContextLookupStrategy;
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+ @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
/** Constructor.*/
public AuthorizationController() {
// PRC -> AC -> nested PRC
@@ -103,8 +121,78 @@ public class AuthorizationController {
// PRC -> AC -> OIDCAuthnContext
oidcContextLookupStrategy = new ChildContextLookup<>(OIDCAuthnContext.class).compose(
new ChildContextLookup<>(AuthenticationContext.class));
+
+
+ // Default under OIDCPeerEntityContext in the outbound context (create true).
+ oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
}
+
+ /**
+ * Set the strategy to lookup the {@link OAuth2ClientContext}
+ * from the {@link ProfileRequestContext}.
+ *
+ * @param strgy the strategy.
+ */
+ public void setOAuth2ClientContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy used to locate the nested {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileRequestContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,ProfileRequestContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ profileRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+ "ProfileRequestContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy used to locate the {@link OIDCAuthnContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setOidcAuthnContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,OIDCAuthnContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ oidcContextLookupStrategy = Constraint.isNotNull(strategy, "OIDCAuthnContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the creation strategy used to compute or lookup a redirect URI.
+ *
+ * @param strategy the creation strategy
+ */
+ public void setRedirectUriCreationStrategy(
+ @Nonnull final BiFunction<HttpServletRequest, ProfileRequestContext, URI> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ redirectUriCreationStrategy =
+ Constraint.isNotNull(strategy, "RedirectURI creation lookup strategy cannot be null");
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (redirectUriCreationStrategy == null) {
+ throw new ComponentInitializationException("redirectUriCreationStrategy cannot be null");
+ }
+ }
/**
* Begin an authorization request to the configured downstream OP.
@@ -116,7 +204,7 @@ public class AuthorizationController {
* @throws IOException throw if there is an error constructing an authz request.
* @throws ExternalAuthenticationException
*/
- @GetMapping("/authz")
+ @GetMapping(AUTHORIZE_PATH_SEGMENT)
public void authorizationRequest(@Nonnull final HttpServletRequest httpRequest,
@Nonnull final HttpServletResponse httpResponse)
throws ServletException, IOException, ExternalAuthenticationException {
@@ -146,6 +234,17 @@ public class AuthorizationController {
final String state = OIDCProxySupport.generateState(OIDCProxySupport.generateNonce(32), key);
((OIDCAuthenticationRequest)nestedPRC.getOutboundMessageContext().getMessage()).setState(new State(state));
+ //add redirect URI based on the request URL and any found in the OIDCAuthnContext
+ final URI redirectUri = redirectUriCreationStrategy.apply(httpRequest, nestedPRC);
+ if (redirectUri == null) {
+ log.error("Redirect URI could not be located or created from strategy");
+ httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_PROFILE_CTX);
+ ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
+ return;
+ }
+ log.trace("Created redirect_uri '{}'", redirectUri);
+ ((OIDCAuthenticationRequest)nestedPRC.getOutboundMessageContext().getMessage()).setRedirectURI(redirectUri);
+
} else {
log.error("Outbound Authorization message not found");
httpRequest.setAttribute(ExternalAuthentication.AUTHENTICATION_ERROR_KEY, EventIds.INVALID_MESSAGE);
@@ -155,7 +254,8 @@ public class AuthorizationController {
try {
if (oidcContext.getOutboundMessageHandler() != null) {
- oidcContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext()); }
+ oidcContext.getOutboundMessageHandler().invoke(nestedPRC.getOutboundMessageContext());
+ }
oidcContext.getEncodeMessageAction().execute(nestedPRC);
// Handle error added by the EncodeMessage action.
@@ -257,4 +357,6 @@ public class AuthorizationController {
ExternalAuthentication.finishExternalAuthentication(key, httpRequest, httpResponse);
}
+
+
}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java
new file mode 100644
index 0000000..b67d742
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunction.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.http.client.utils.URIBuilder;
+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 net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Constructive, pure, function that returns a redirect_uri from one of (ordered):
+ * <ol>
+ * <li>A pre-registered redirect_uri on the {@link OAuth2ClientContext#getRedirectUriOverride()}.
+ * Or, if none are pre-registered;
+ * <li>Derived from the HTTP Servlet request server parameters, checking the origin
+ * against an allowed set of origins - to prevent Host header injection.
+ * </ol>
+ *
+ * <p>Returns null if one can not be constructed.</p>
+ *
+ * <p>Is thread-safe and immutable</p>
+ */
+ at ThreadSafe
+ at Immutable
+//TODO similar to that used in the Duo plugin.
+public class DefaultRedirectUriCreationFunction
+ implements BiFunction<HttpServletRequest, ProfileRequestContext, URI> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultRedirectUriCreationFunction.class);
+
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+ @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+ /** The path, excluding the context and servlet paths, to the Duo callback handler.*/
+ @Nonnull @NotEmpty private final String callbackServletPath;
+
+ /**
+ * A set of 'allowed' origins that can be used as the scheme, host, and port portion of the redirectURI.
+ * Can be null, if so a redirect_uri must be specified in the context tree.
+ */
+ @Nullable private Set<String> allowedOrigins;
+
+ /**
+ * Constructor.
+ *
+ * @param callbackPath the path segment relative to the servlet path of the callback endpoint.
+ * @param origins the allowed origins to use if a redirect_uri is computed
+ */
+ public DefaultRedirectUriCreationFunction(
+ @Nonnull @NotEmpty @ParameterName(name="callbackPath") final String callbackPath,
+ @Nonnull @NotEmpty @ParameterName(name="allowedOrigins") @Nullable final Set<String> origins) {
+
+ callbackServletPath = Constraint.isNotNull(callbackPath,"Duo Call back path can not be null");
+ // Default under OIDCPeerEntityContext in the outbound context (create true) under the nested PRC.
+ oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ if (origins == null) {
+ allowedOrigins = Collections.emptySet();
+ } else {
+ allowedOrigins = Collections.unmodifiableSet(origins);
+ }
+ }
+
+ /**
+ * Set the allowed origins.
+ *
+ * @param origins the origins
+ */
+ public void setAllowedOrigins(@Nullable final Set<String> origins) {
+ if (origins == null) {
+ allowedOrigins = Collections.emptySet();
+ }
+ allowedOrigins = Collections.unmodifiableSet(origins);
+ }
+
+
+ /**
+ * Set the strategy to lookup the {@link OAuth2ClientContext}
+ * from the {@link ProfileRequestContext}.
+ *
+ * @param strgy the strategy.
+ */
+ public void setOAuth2ClientContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+
+ oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
+ }
+
+ @Override
+ @Nullable public URI apply(@Nonnull final HttpServletRequest request, @Nonnull final ProfileRequestContext prc) {
+
+ final OAuth2ClientContext context = oauth2ClientContextLookupStrategy.apply(prc);
+ if (context == null) {
+ log.warn("Could not locate the OAuth2 Client Context, can not compute redirect_uri");
+ return null;
+ }
+ if (context.getRedirectUriOverride() != null) {
+ return context.getRedirectUriOverride();
+ }
+
+ // Should be caught upstream, but dbl check.
+ if (allowedOrigins.isEmpty()) {
+ log.warn("Can not compute redirect_uri if allowed origins is empty");
+ return null;
+ }
+
+
+ try {
+ final URI redirectUri = buildURIIgnoreDefaultPorts(request.getScheme(),
+ request.getServerName(),
+ request.getServerPort(),
+ request.getContextPath()+request.getServletPath()+callbackServletPath);
+
+ final String origin = buildOrigin(redirectUri);
+ if (!allowedOrigins.contains(origin)) {
+ log.warn("The 'origin' of the computed redirect_uri ('{}') is not allowed. If permissible, add it "
+ + "to the allowed origins property.",origin);
+ return null;
+ }
+ return redirectUri;
+ } catch (final URISyntaxException e) {
+ log.warn("Unable to create redirect_uri for OIDC authentication request", e);
+ return null;
+ }
+ }
+
+ /**
+ * Builds the 'origin' (see RFC 6454) from given URI. Omits default or unknown ports.
+ *
+ * @param uri the URI to build the origin from
+ * @return the origin
+ * @throws URISyntaxException if there is an error getting information from the URI.
+ */
+ @Nonnull private String buildOrigin(@Nonnull final URI uri) throws URISyntaxException {
+
+ if (uri.getPort() == -1) {
+ //is the default port (or is not defined), do not include
+ return new URI(String.format("%s://%s", uri.getScheme(),uri.getHost())).toString();
+ } else {
+ return new URI(String.format("%s://%s:%s", uri.getScheme(),uri.getHost(),uri.getPort())).toString();
+ }
+
+ }
+
+ /**
+ * Build a {@link URI} from the given parameters. If the scheme is either
+ * 'http' or 'https' with their respective default port, the port is set to -1.
+ *
+ * @param scheme the scheme
+ * @param host the hostname
+ * @param port the port
+ * @param path the path
+ *
+ * @return a fully built URI from the given parameters.
+ *
+ * @throws URISyntaxException if the URI can not be constructed.
+ */
+ @Nonnull private final URI buildURIIgnoreDefaultPorts(@Nonnull final String scheme,
+ @Nonnull final String host, @Nonnull final int port,
+ @Nonnull final String path) throws URISyntaxException {
+
+ int usedPort = port;
+ if ("http".equalsIgnoreCase(scheme)) {
+ // ignore port iff using the default http port
+ if (port == 80) {
+ usedPort = -1;
+ }
+ } else if ("https".equalsIgnoreCase(scheme)) {
+ // ignore port iff using the default https port
+ if (port == 443) {
+ usedPort = -1;
+ }
+ }
+ return new URIBuilder()
+ .setScheme(scheme)
+ .setHost(host)
+ .setPort(usedPort)
+ .setPath(path)
+ .build();
+ }
+
+}
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
index f2f5eaf..7536bfa 100644
--- 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
@@ -35,6 +35,7 @@ import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
* 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.
*/
+//TODO move to checking profile config!
public class DefaultResponseTypeLookupFunction implements Function<ProfileRequestContext, ResponseType> {
/** The Default response type if none is selected.*/
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
new file mode 100644
index 0000000..9ff116a
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientAuthenticationContext.java
@@ -0,0 +1,197 @@
+/*
+ * 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.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.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.oidc.metadata.criterion.ClientIDCriterion;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+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;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * An {@link AbstractProfileAction action} that resolves the client authentication method for the chosen
+ * downstream provider (issuer).
+ */
+public class InitializeOAuth2ClientAuthenticationContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientAuthenticationContext.class);
+
+ /** The resolver to use to find the client authentication for the given (client_id). */
+ @NonnullAfterInit private OAuth2ClientAuthenticationResolver clientAuthenticationResovler;
+
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext}.*/
+ @Nonnull private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client authentication.*/
+ @Nonnull private
+ Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> oauth2ClientAuthenticationContextLookupStrategy;
+
+ /** The stashed OAuth2 client context.*/
+ @Nullable private OAuth2ClientContext oauth2ClientContext;
+
+ /** The stashed OAuth2 client authentication context.*/
+ @Nullable private OAuth2ClientAuthenticationContext oauth2ClientAuthenticationContext;
+
+
+ /** Constructor.*/
+ public InitializeOAuth2ClientAuthenticationContext() {
+ // Default under OIDCPeerEntityContext in the outbound context (create true).
+ oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ oauth2ClientAuthenticationContextLookupStrategy =
+ new ChildContextLookup<>(OAuth2ClientAuthenticationContext.class, true).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (clientAuthenticationResovler == null) {
+ throw new ComponentInitializationException("OAuth2 Client Authentication Resovler cannot be null");
+ }
+ }
+
+ /**
+ * Set the OAuth2 client authentication resolver.
+ *
+ * @param resolver the resolver
+ */
+ public void setClientAuthenticationResolver(@Nonnull final OAuth2ClientAuthenticationResolver resolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ clientAuthenticationResovler = Constraint.isNotNull(resolver,
+ "OAuth2 Client Authentication Resovler cannot be null");
+ }
+
+ /**
+ * Set the strategy to lookup the {@link OAuth2ClientContext}
+ * from the {@link ProfileRequestContext}.
+ *
+ * @param strgy the strategy.
+ */
+ public void setOAuth2ClientContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext}
+ * from the {@link ProfileRequestContext}.
+ *
+ * @param strgy the strategy.
+ */
+ public void setOAuth2ClientAuthenticationContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientAuthenticationContext> strgy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ oauth2ClientAuthenticationContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client authentication context lookup strategy cannot be null");
+ }
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+ if (oauth2ClientContext == null) {
+ log.error("{} No OAuth2 client context found or created", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ oauth2ClientAuthenticationContext =
+ oauth2ClientAuthenticationContextLookupStrategy.apply(profileRequestContext);
+ if (oauth2ClientAuthenticationContext == null) {
+ log.error("{} No OAuth2 client authentication context found or created", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+
+ }
+
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ super.doExecute(profileRequestContext);
+
+ try {
+ final ClientAuthentication clientAuth =
+ clientAuthenticationResovler.resolveSingle(
+ new CriteriaSet(new ClientIDCriterion(new ClientID(oauth2ClientContext.getClientId()))));
+
+ if (clientAuth == null) {
+ log.error("{} No client authentication mode found from resolver", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+ return;
+ }
+
+ log.trace("{} Found client authentication mode '{}' for client '{}'",
+ getLogPrefix(), clientAuth.getMethod(), oauth2ClientContext.getClientId());
+
+ oauth2ClientAuthenticationContext.setClientAuthentication(clientAuth);
+ log.debug("{} Initialized OAuth2 Client Authentication Context for client '{}'",
+ getLogPrefix(), oauth2ClientContext.getClientId());
+ } catch (final ResolverException e) {
+ log.warn("{} client context could not be initialized", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+ return;
+ }
+ }
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java
new file mode 100644
index 0000000..6415634
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/InitializeOAuth2ClientContext.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.RedirectUriLookupFunction;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.metadata.criterion.ClientIDCriterion;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+/**
+ * An {@link AbstractProfileAction action} that resolves the client identifier for the chosen
+ * downstream provider (issuer).
+ */
+public class InitializeOAuth2ClientContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientContext.class);
+
+ /** The resolver to use to find the client identifier (client_id). */
+ @NonnullAfterInit private OAuth2ClientIdentifierResolver clientIdResovler;
+
+ /** The strategy used to lookup or create the {@link OAuth2ClientContext} for storing the client_id.*/
+ @NonnullAfterInit private Function<ProfileRequestContext, OAuth2ClientContext> oauth2ClientContextLookupStrategy;
+
+ /** The stashed OAuth2 client context.*/
+ @Nullable private OAuth2ClientContext oauth2ClientContext;
+
+ /** A redirect_uri lookup strategy which can pull out an override redirect_uri from the profile request context.*/
+ @Nonnull private Function<ProfileRequestContext, URI> redirectUriOverrideLookupStrategy;
+
+ /** Constructor.*/
+ public InitializeOAuth2ClientContext() {
+ // Default under OIDCPeerEntityContext in the outbound context (create true).
+ oauth2ClientContextLookupStrategy = new ChildContextLookup<>(OAuth2ClientContext.class, true).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ redirectUriOverrideLookupStrategy = new RedirectUriLookupFunction();
+ }
+
+ /**
+ * Set the redirect_uri lookup strategy to locate an explicitly set redirect_uri.
+ *
+ * @param strategy the strategy.
+ */
+ public void setRedirectUriOverrideLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, URI> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ redirectUriOverrideLookupStrategy =
+ Constraint.isNotNull(strategy, "Redirect URI lookup strategy can not be null");
+ }
+
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (clientIdResovler == null) {
+ throw new ComponentInitializationException("OAuth2 Client Resovler cannot be null");
+ }
+ }
+
+ /**
+ * Set the OAuth2 client identifier (client_id) resolver.
+ *
+ * @param resolver the resolver
+ */
+ public void setClientIdResolver(@Nonnull final OAuth2ClientIdentifierResolver resolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ clientIdResovler = Constraint.isNotNull(resolver, "OAuth2 Client Resovler cannot be null");
+ }
+
+
+ /**
+ * Set the strategy to lookup the {@link OAuth2ClientContext}
+ * from the {@link ProfileRequestContext}.
+ *
+ * @param strgy the strategy.
+ */
+ public void setOAuth2ClientContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+ oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
+ "OAuth2 client context lookup strategy cannot be null");
+ }
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ oauth2ClientContext = oauth2ClientContextLookupStrategy.apply(profileRequestContext);
+ if (oauth2ClientContext == null) {
+ log.error("{} No OAuth2 client context found or created", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ super.doExecute(profileRequestContext);
+
+ try {
+ final String clientId = clientIdResovler.resolveSingle(profileRequestContext);
+ if (clientId == null) {
+ log.error("{} No client_id found from resolver", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+ return;
+ }
+ oauth2ClientContext.setClientId(clientId);
+
+ final URI redirectUri = redirectUriOverrideLookupStrategy.apply(profileRequestContext);
+ if (redirectUri != null) {
+ log.debug("{} Redirect_uri has been explicitly set as '{}'", getLogPrefix(), redirectUri);
+ oauth2ClientContext.setRedirectUriOverride(redirectUri);
+ }
+ log.debug("{} Initialized OAuth2 Client Context for client '{}'", getLogPrefix(), clientId);
+ } catch (final ResolverException e) {
+ log.warn("{} client context could not be initialized", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CONFIG);
+ 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 00e5dc0..30d6522 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
@@ -50,10 +50,7 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
/** The {@link OIDCPeerEntityContext} to base the outbound context on. */
@Nullable private OIDCPeerEntityContext peerEntityCtx;
-
- /** The stashed inbound client metadata context.*/
- @Nullable private OIDCMetadataContext inboundClientMetadata;
-
+
/** Strategy function to lookup the {@link OIDCMetadataContext} that represents this client during
* communication with the given OIDC peer. */
@Nonnull
@@ -125,15 +122,6 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
peerEntityCtx = (OIDCPeerEntityContext) identifyingCtx;
- inboundClientMetadata =
- oidcClientMetadataCtxLookupStrategy.apply(profileRequestContext);
-
- if (inboundClientMetadata == null) {
- log.debug("{} No OIDC inbound client metadata context", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
- return false;
- }
-
return true;
}
@@ -159,10 +147,6 @@ public class InitializeOutboundAuthorizationRequestMessageContext extends Abstra
}
- final OIDCMetadataContext outboundClientMetadata = new OIDCMetadataContext();
- 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/MapBackedMemoryStorageServiceFactoryBean.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MapBackedMemoryStorageServiceFactoryBean.java
new file mode 100644
index 0000000..10f5328
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/MapBackedMemoryStorageServiceFactoryBean.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.Collections;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.springframework.beans.factory.config.AbstractFactoryBean;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+
+/**
+ * A factory that creates and populates a map backed MemoryStorageService from an injected map.
+ * Values which are *not* Strings are serialized into JSON using the supplied object mapper before they are added
+ * to the storage service. Values which are already Strings are just passed in without modification.
+ */
+public class MapBackedMemoryStorageServiceFactoryBean extends AbstractFactoryBean<MemoryStorageService> {
+
+ /** The Map to inject values into the storage service with.*/
+ @Nonnull private final Map<String, Object> mapToInject;
+
+ /** The ID to give the created memory service.*/
+ @Nonnull private final String identifier;
+
+ /** The partition name to use in the injected storage service.*/
+ @Nonnull private final String storageServiceContext;
+
+ /** JSON object mapper. */
+ @Nonnull @GuardedBy("this") private ObjectMapper objectMapper;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param map the issuer to client_id map to base the storage service off
+ * @param id The ID to give the created memory service
+ * @param context The partition name to use in the injected storage service
+ */
+ public MapBackedMemoryStorageServiceFactoryBean(@ParameterName(name="context") @Nonnull final String context,
+ @ParameterName(name="map") @Nullable final Map<String, Object> map,
+ @ParameterName(name="id") @Nonnull final String id) {
+ if (map == null) {
+ mapToInject = Collections.emptyMap();
+ } else {
+ mapToInject = Collections.unmodifiableMap(map);
+ }
+ identifier = Constraint.isNotEmpty(id, "Memory storage service must have an ID");
+ storageServiceContext = Constraint.isNotEmpty(context, "Storage service context can not be null");
+ setObjectMapper(new ObjectMapper());
+ }
+
+ /**
+ * Set the JSON object mapper to use to convert the client authentication details to a JSON string.
+ *
+ * @param mapper the object mapper
+ */
+ public synchronized void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper can not be null");
+ }
+
+ /**
+ * Get the JSON object mapper.
+ *
+ * @return the object mapper
+ */
+ private synchronized ObjectMapper getObjectMapper() {
+ return objectMapper;
+ }
+
+ @Override
+ public Class<?> getObjectType() {
+ return MemoryStorageService.class;
+ }
+
+ @Override
+ protected MemoryStorageService createInstance() throws Exception {
+ final MemoryStorageService service = new MemoryStorageService();
+ service.setId(identifier);
+ service.initialize();
+ // One writer for all records, is safe.
+ final ObjectWriter ow = getObjectMapper().writer();
+ for (final Map.Entry<String, Object> entry : mapToInject.entrySet()) {
+ String objectAsString = null;
+ if (entry.getValue() instanceof String) {
+ objectAsString = (String) entry.getValue();
+ } else {
+ objectAsString = ow.writeValueAsString(entry.getValue());
+ }
+ service.create(storageServiceContext, entry.getKey(), objectAsString, null);
+ }
+ return service;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java
new file mode 100644
index 0000000..463c1af
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationContainer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.spring.impl;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+
+import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
+import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+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;
+
+/**
+ * This class is a sortable container of {@link OAuth2ClientAuthenticationResolver}s, wrapped into a serviceable component.
+ */
+public class OAuth2ClientAuthenticationContainer extends AbstractServiceableComponent<OAuth2ClientAuthenticationResolver>
+ implements Comparable<OAuth2ClientAuthenticationContainer> {
+
+ /** If we autogenerate a sort key it comes from this count. */
+ private static int sortKeyValue;
+
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(OAuth2ClientAuthenticationContainer.class);
+
+ /** The embedded resolver. */
+ @NonnullAfterInit
+ private OAuth2ClientAuthenticationResolver resolver;
+
+ /** The key by which we sort the provider. */
+ @NonnullAfterInit
+ private Integer sortKey;
+
+ /**
+ * Set the sort key.
+ *
+ * @param key what to set
+ */
+ public void setSortKey(final int key) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ sortKey = key;
+ }
+
+ /**
+ * Set the {@link ProviderMetadataResolver} to embed.
+ *
+ * @param theResolver The {@link ProviderMetadataResolver} to embed.
+ */
+ @Nonnull
+ public void setEmbeddedResolver(@Nonnull final OAuth2ClientAuthenticationResolver theResolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ resolver = Constraint.isNotNull(theResolver, "OAuth2ClientAuthenticationResolver cannot be null");
+ }
+
+ /**
+ * Return what we are build around. Used for testing.
+ *
+ * @return the parameter we got as a constructor
+ */
+ @Nonnull
+ public OAuth2ClientAuthenticationResolver getEmbeddedResolver() {
+ return resolver;
+ }
+
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ setId(resolver.getId());
+ super.doInitialize();
+ if (null == resolver) {
+ throw new ComponentInitializationException("OAuth2ClientAuthenticationResolver cannot be null");
+ }
+
+ if (null == sortKey) {
+ synchronized (OAuth2ClientAuthenticationContainer.class) {
+ sortKeyValue++;
+ setSortKey(sortKeyValue);
+ }
+ log.info("Top level OAuth2ClientAuthentication Provider '{}' "
+ + "did not have a sort key; giving it value '{}'", getId(),
+ sortKey);
+ }
+ }
+
+
+ @Override
+ @Nonnull
+ public OAuth2ClientAuthenticationResolver getComponent() {
+ return getEmbeddedResolver();
+ }
+
+ @Override
+ public int compareTo(final OAuth2ClientAuthenticationContainer other) {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ final int result = sortKey.compareTo(other.sortKey);
+ if (result != 0) {
+ return result;
+ }
+ if (equals(other)) {
+ return 0;
+ }
+ return getId().compareTo(other.getId());
+ }
+
+ /**
+ * {@inheritDoc}. We are within a spring context and so equality can be determined by ID, however we also test by
+ * sortKey just in case.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (null == other) {
+ return false;
+ }
+ if (!(other instanceof OAuth2ClientAuthenticationContainer)) {
+ return false;
+ }
+ final OAuth2ClientAuthenticationContainer otherRp = (OAuth2ClientAuthenticationContainer) other;
+
+ return Objects.equal(otherRp.sortKey, sortKey) && Objects.equal(getId(), otherRp.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(sortKey, getId());
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java
new file mode 100644
index 0000000..7b7ffaa
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientAuthenticationResolverServiceStrategy.java
@@ -0,0 +1,65 @@
+/*
+ * 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.spring.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.ApplicationContext;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+public class OAuth2ClientAuthenticationResolverServiceStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<ApplicationContext, ServiceableComponent<OAuth2ClientAuthenticationResolver>> {
+
+ @Override
+ public ServiceableComponent<OAuth2ClientAuthenticationResolver> apply(
+ @Nullable final ApplicationContext appContext) {
+ final Collection<OAuth2ClientAuthenticationContainer> resolvers =
+ appContext.getBeansOfType(OAuth2ClientAuthenticationContainer.class).values();
+
+ if (resolvers.isEmpty()) {
+ throw new ServiceException(
+ "Reload did not produce any bean of type " + OAuth2ClientAuthenticationContainer.class.getName());
+ }
+ if (1 == resolvers.size()) {
+ // done
+ return resolvers.iterator().next();
+ }
+ // initialize so we can sort
+ for (final OAuth2ClientAuthenticationContainer resolver : resolvers) {
+ try {
+ resolver.initialize();
+ } catch (final ComponentInitializationException e) {
+ throw new BeanCreationException("Could not preinitialize "
+ + resolver.getId(), e);
+ }
+ }
+
+ throw new BeanCreationException("could not preinitialize the Client Authentication Provider, "
+ + "mulitple Client Authentication Providers not supported");
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java
new file mode 100644
index 0000000..601200d
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierContainer.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Objects;
+
+import net.shibboleth.ext.spring.service.AbstractServiceableComponent;
+import net.shibboleth.oidc.metadata.ProviderMetadataResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+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;
+
+/**
+ * This class is a sortable container of {@link OAuth2ClientAuthenticationResolver}s, wrapped into a serviceable component.
+ */
+public class OAuth2ClientIdentifierContainer extends AbstractServiceableComponent<OAuth2ClientIdentifierResolver>
+ implements Comparable<OAuth2ClientIdentifierContainer> {
+
+ /** If we autogenerate a sort key it comes from this count. */
+ private static int sortKeyValue;
+
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(OAuth2ClientIdentifierContainer.class);
+
+ /** The embedded resolver. */
+ @NonnullAfterInit
+ private OAuth2ClientIdentifierResolver resolver;
+
+ /** The key by which we sort the provider. */
+ @NonnullAfterInit
+ private Integer sortKey;
+
+ /**
+ * Set the sort key.
+ *
+ * @param key what to set
+ */
+ public void setSortKey(final int key) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ sortKey = key;
+ }
+
+ /**
+ * Set the {@link OAuth2ClientIdentifierResolver} to embed.
+ *
+ * @param theResolver The {@link OAuth2ClientIdentifierResolver} to embed.
+ */
+ @Nonnull
+ public void setEmbeddedResolver(@Nonnull final OAuth2ClientIdentifierResolver theResolver) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+ resolver = Constraint.isNotNull(theResolver, "OAuth2ClientAuthenticationResolver cannot be null");
+ }
+
+ /**
+ * Return what we are built around. Used for testing.
+ *
+ * @return the embedded resolver.
+ */
+ @Nonnull
+ public OAuth2ClientIdentifierResolver getEmbeddedResolver() {
+ return resolver;
+ }
+
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ setId(resolver.getId());
+ super.doInitialize();
+ if (null == resolver) {
+ throw new ComponentInitializationException("OAuth2ClientAuthenticationResolver cannot be null");
+ }
+
+ if (null == sortKey) {
+ synchronized (OAuth2ClientIdentifierContainer.class) {
+ sortKeyValue++;
+ setSortKey(sortKeyValue);
+ }
+ log.info("Top level OAuth2ClientAuthentication Provider '{}' "
+ + "did not have a sort key; giving it value '{}'", getId(),
+ sortKey);
+ }
+ }
+
+
+ @Override
+ @Nonnull
+ public OAuth2ClientIdentifierResolver getComponent() {
+ return getEmbeddedResolver();
+ }
+
+ @Override
+ public int compareTo(final OAuth2ClientIdentifierContainer other) {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ final int result = sortKey.compareTo(other.sortKey);
+ if (result != 0) {
+ return result;
+ }
+ if (equals(other)) {
+ return 0;
+ }
+ return getId().compareTo(other.getId());
+ }
+
+ /**
+ * {@inheritDoc}. We are within a spring context and so equality can be determined by ID, however we also test by
+ * sortKey just in case.
+ */
+ @Override
+ public boolean equals(final Object other) {
+ if (null == other) {
+ return false;
+ }
+ if (!(other instanceof OAuth2ClientIdentifierContainer)) {
+ return false;
+ }
+ final OAuth2ClientIdentifierContainer otherRp = (OAuth2ClientIdentifierContainer) other;
+
+ return Objects.equal(otherRp.sortKey, sortKey) && Objects.equal(getId(), otherRp.getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(sortKey, getId());
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java
new file mode 100644
index 0000000..0820475
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/OAuth2ClientIdentifierResolverServiceStrategy.java
@@ -0,0 +1,67 @@
+/*
+ * 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.spring.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.ApplicationContext;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.service.ServiceException;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+public class OAuth2ClientIdentifierResolverServiceStrategy extends AbstractIdentifiableInitializableComponent
+ implements Function<ApplicationContext, ServiceableComponent<OAuth2ClientIdentifierResolver>> {
+
+ @Override
+ public ServiceableComponent<OAuth2ClientIdentifierResolver> apply(
+ @Nullable final ApplicationContext appContext) {
+
+ final Collection<OAuth2ClientIdentifierContainer> resolvers =
+ appContext.getBeansOfType(OAuth2ClientIdentifierContainer.class).values();
+
+ if (resolvers.isEmpty()) {
+ throw new ServiceException(
+ "Reload did not produce any bean of type " + OAuth2ClientAuthenticationContainer.class.getName());
+ }
+ if (1 == resolvers.size()) {
+ // done
+ return resolvers.iterator().next();
+ }
+ // initialize so we can sort
+ for (final OAuth2ClientIdentifierContainer resolver : resolvers) {
+ try {
+ resolver.initialize();
+ } catch (final ComponentInitializationException e) {
+ throw new BeanCreationException("Could not preinitialize the Client Identifer Provider "
+ + resolver.getId(), e);
+ }
+ }
+
+ throw new BeanCreationException("Could not preinitialize the Client Identifer Provider"
+ + ", mulitple client identifer providers not supported");
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java
new file mode 100644
index 0000000..62dee46
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientAuthenticationProvider.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+
+/**
+ * A service interface to implement the {@link OAuth2ClientAuthenticationResolver}.
+ */
+public class ReloadingOAuth2ClientAuthenticationProvider extends AbstractIdentifiableInitializableComponent
+ implements OAuth2ClientAuthenticationResolver {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadingOAuth2ClientAuthenticationProvider.class);
+
+ /** The service which manages the reloading. */
+ private final ReloadableService<OAuth2ClientAuthenticationResolver> service;
+
+ /**
+ * Constructor.
+ *
+ * @param resolverService the service which will manage the loading.
+ */
+ public ReloadingOAuth2ClientAuthenticationProvider(
+ @Nonnull final ReloadableService<OAuth2ClientAuthenticationResolver> resolverService) {
+ service = Constraint.isNotNull(resolverService, "ProviderMetadataResolver Service cannot be null");
+ }
+
+ @Override
+ public Iterable<ClientAuthentication> resolve(@Nonnull final CriteriaSet criteria) throws ResolverException {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ ServiceableComponent<OAuth2ClientAuthenticationResolver> component = null;
+ try {
+ component = service.getServiceableComponent();
+ if (null == component) {
+ log.error("OAuth2ClientAuthenticationResolver '{}': Error accessing underlying source: "
+ + "Invalid configuration.", getId());
+ } else {
+ final OAuth2ClientAuthenticationResolver resolver = component.getComponent();
+ return resolver.resolve(criteria);
+ }
+ } catch (final ResolverException e) {
+ log.error("OAuth2ClientAuthenticationResolver '{}': Error during resolution", getId(), e);
+ } finally {
+ if (null != component) {
+ component.unpinComponent();
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public ClientAuthentication resolveSingle(@Nonnull final CriteriaSet criteria) throws ResolverException {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ ServiceableComponent<OAuth2ClientAuthenticationResolver> component = null;
+ try {
+ component = service.getServiceableComponent();
+ if (null == component) {
+ log.error("ReloadingProviderMetadataProvider '{}': Error accessing underlying source: "
+ + "Invalid configuration.", getId());
+ } else {
+ final OAuth2ClientAuthenticationResolver resolver = component.getComponent();
+ return resolver.resolveSingle(criteria);
+ }
+ } catch (final ResolverException e) {
+ log.error("ProviderMetadataResolver '{}': Error during resolution", getId(), e);
+ } finally {
+ if (null != component) {
+ component.unpinComponent();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.java
new file mode 100644
index 0000000..5d4ee68
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/spring/impl/ReloadingOAuth2ClientIdentifierProvider.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.spring.impl;
+
+import java.util.Collections;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver;
+import net.shibboleth.utilities.java.support.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
+
+/**
+ * A service interface to implement the {@link OAuth2ClientIdentifierResolver}.
+ */
+public class ReloadingOAuth2ClientIdentifierProvider extends AbstractIdentifiableInitializableComponent
+ implements OAuth2ClientIdentifierResolver {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(ReloadingOAuth2ClientIdentifierProvider.class);
+
+ /** The service which manages the reloading. */
+ private final ReloadableService<OAuth2ClientIdentifierResolver> service;
+
+ /**
+ * Constructor.
+ *
+ * @param resolverService the service which will manage the loading.
+ */
+ public ReloadingOAuth2ClientIdentifierProvider(
+ @Nonnull final ReloadableService<OAuth2ClientIdentifierResolver> resolverService) {
+ service = Constraint.isNotNull(resolverService, "ProviderMetadataResolver Service cannot be null");
+ }
+
+ @Override
+ public Iterable<String> resolve(@Nonnull final ProfileRequestContext criteria) throws ResolverException {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ ServiceableComponent<OAuth2ClientIdentifierResolver> component = null;
+ try {
+ component = service.getServiceableComponent();
+ if (null == component) {
+ log.error("OAuth2ClientAuthenticationResolver '{}': Error accessing underlying source: "
+ + "Invalid configuration.", getId());
+ } else {
+ final OAuth2ClientIdentifierResolver resolver = component.getComponent();
+ return resolver.resolve(criteria);
+ }
+ } catch (final ResolverException e) {
+ log.error("OAuth2ClientAuthenticationResolver '{}': Error during resolution", getId(), e);
+ } finally {
+ if (null != component) {
+ component.unpinComponent();
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public String resolveSingle(@Nonnull final ProfileRequestContext criteria) throws ResolverException {
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ ServiceableComponent<OAuth2ClientIdentifierResolver> component = null;
+ try {
+ component = service.getServiceableComponent();
+ if (null == component) {
+ log.error("ReloadingProviderMetadataProvider '{}': Error accessing underlying source: "
+ + "Invalid configuration.", getId());
+ } else {
+ final OAuth2ClientIdentifierResolver resolver = component.getComponent();
+ return resolver.resolveSingle(criteria);
+ }
+ } catch (final ResolverException e) {
+ log.error("ProviderMetadataResolver '{}': Error during resolution", getId(), e);
+ } finally {
+ if (null != component) {
+ component.unpinComponent();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index b464d4d..f9b0fc8 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -14,6 +14,46 @@
The default template shows an incomplete example authentication flow descriptor which can be
removed if not needed
-->
+
+ <!-- Functions use by the flow and global beans -->
+
+ <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }" />
+
+ <bean id="shibboleth.ChildLookup.OAuth2ClientContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext) }" />
+
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext) }" />
+
+ <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound" parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext"/>
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.ChildLookup.OAuth2ClientContextFromOutbound" parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.OAuth2ClientContext"/>
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound"/>
+ </constructor-arg>
+ </bean>
+
+ <!-- Find OIDCPeerEntity Context under outbound message -->
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+
+
+ <!-- The authentication flow descriptor -->
<bean id="authn/OIDCRelyingParty" parent="shibboleth.AuthenticationFlow"
p:order="%{idp.authn.oidc.rp.order:1000}"
@@ -98,8 +138,25 @@
</bean>
<!-- Controller implementation -->
- <bean id="shibboleth.oidc.rp.OpenIDConnectStartServlet"
+ <bean id="shibboleth.authn.OIDC.externalServletPath" class="java.lang.String"
+ c:_0="%{idp.authn.oidc.rp.externalAuthnPath:/Authn/OIDC/RP}">
+ </bean>
+
+ <!-- location of the OIDC servlet authorization endpoint releative to the servlet -->
+ <bean id="shibboleth.authn.OIDC.externalAuthnPath" class="java.lang.String"
+ c:_0="servletRelative:#{getObject('shibboleth.authn.OIDC.externalServletPath')}#{T(net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController).AUTHORIZE_PATH_SEGMENT}" />
+
+
+
+ <bean id="shibboleth.oidc.rp.AuthorizationController"
+ p:redirectUriCreationStrategy="#{getObject('shibboleth.oidc.rp.RedirectUriCreationStrategy') ?: getObject('shibboleth.oidc.rp.DefaultRedirectUriCreationStrategy')}"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AuthorizationController" />
+
+
+ <bean id="shibboleth.oidc.rp.DefaultRedirectUriCreationStrategy"
+ c:callbackPath="#{getObject('shibboleth.authn.OIDC.externalServletPath')}/callback"
+ c:allowedOrigins="%{idp.oidc.rp.redirecturl.allowedOrigins:}"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.DefaultRedirectUriCreationFunction"/>
<!-- TODO these bean names need to change - otherwise they may class with the OP plugin if installed
at the same time -->
@@ -145,40 +202,72 @@
</bean>
- <!-- OIDC client information resolver service beans. -->
-
+ <!-- Client authentication resolvers -->
<!-- TODO add this back? depends-on="shibboleth.AttributeResolverService"-->
-
- <bean id="shibboleth.oidc.rp.ClientInformationResolverService"
+
+ <bean id="shibboleth.oidc.rp.ClientAuthenticationResolverService"
class="net.shibboleth.ext.spring.service.ReloadableSpringService"
- p:serviceConfigurations-ref="ExtendedClientInformationResolverResources"
+ p:serviceConfigurations-ref="ExtendedClientAuthenticationResolverResources"
p:failFast="%{idp.service.clientinfo.failFast:%{idp.service.failFast:false}}"
p:reloadCheckDelay="%{idp.service.clientinfo.checkInterval:PT0S}"
p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
<constructor-arg name="claz"
- value="net.shibboleth.oidc.metadata.ClientInformationResolver" />
+ value="net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolver" />
<constructor-arg name="strategy">
- <bean class="net.shibboleth.oidc.profile.spring.relyingparty.metadata.impl.ClientInformationResolverServiceStrategy" />
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientAuthenticationResolverServiceStrategy" />
</constructor-arg>
</bean>
- <util:list id="shibboleth.DefaultClientInformationResolverResources">
- <value>%{idp.home}/conf/authn/oidc-clientinfo-resolvers.xml</value> <!-- should be a conditional:? -->
+
+ <bean id ="ExtendedClientAuthenticationResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
+ p:firstList="#{getObject('%{idp.service.clientinfo.resources:shibboleth.ClientInformationResolverResources}'.trim()) ?:
+ getObject('shibboleth.DefaultClientAuthenticationResolverResources')}">
+ <property name="secondList">
+ <util:list >
+ <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml</value>
+ </util:list>
+ </property>
+ </bean>
+ <util:list id="shibboleth.DefaultClientAuthenticationResolverResources">
+ <value>%{idp.home}/conf/authn/oidc-client-registration-authentication.xml</value> <!-- should be a conditional:? -->
</util:list>
- <!-- Auto-append system config file to resource set. -->
- <bean id ="ExtendedClientInformationResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolver"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.ReloadingOAuth2ClientAuthenticationProvider"
+ c:resolverService-ref="shibboleth.oidc.rp.ClientAuthenticationResolverService" />
+
+
+ <!-- Client Identifier resolvers -->
+ <bean id="shibboleth.oidc.rp.ClientIdentifierResolverService"
+ class="net.shibboleth.ext.spring.service.ReloadableSpringService"
+ p:serviceConfigurations-ref="ExtendedClientIdentifierResolverResources"
+ p:failFast="%{idp.service.clientinfo.failFast:%{idp.service.failFast:false}}"
+ p:reloadCheckDelay="%{idp.service.clientinfo.checkInterval:PT0S}"
+ p:beanPostProcessors-ref="shibboleth.IdentifiableBeanPostProcessor"
+ p:beanFactoryPostProcessors-ref="shibboleth.PropertySourcesPlaceholderConfigurer">
+ <constructor-arg name="claz"
+ value="net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolver" />
+ <constructor-arg name="strategy">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientIdentifierResolverServiceStrategy" />
+ </constructor-arg>
+ </bean>
+
+ <bean id ="ExtendedClientIdentifierResolverResources" class="net.shibboleth.ext.spring.factory.CombiningListFactoryBean"
p:firstList="#{getObject('%{idp.service.clientinfo.resources:shibboleth.ClientInformationResolverResources}'.trim()) ?:
- getObject('shibboleth.DefaultClientInformationResolverResources')}">
+ getObject('shibboleth.DefaultClientIdentifierResolverResources')}">
<property name="secondList">
<util:list >
- <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientinfo-resolver-system.xml</value>
+ <value>classpath:/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml</value>
</util:list>
</property>
</bean>
-
- <bean id="shibboleth.oidc.rp.ClientInformationResolver"
- class="net.shibboleth.oidc.metadata.impl.ReloadingRelyingPartyClientInformationProvider"
- c:resolverService-ref="shibboleth.oidc.rp.ClientInformationResolverService" />
+ <util:list id="shibboleth.DefaultClientIdentifierResolverResources">
+ <value>%{idp.home}/conf/authn/oidc-client-registration-clientid.xml</value> <!-- should be a conditional:? -->
+ </util:list>
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierResolver"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.ReloadingOAuth2ClientIdentifierProvider"
+ c:resolverService-ref="shibboleth.oidc.rp.ClientIdentifierResolverService" />
-
+
</beans>
\ No newline at end of file
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 d49e603..84de395 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
@@ -21,14 +21,8 @@
<!-- Initial discovery step -->
<bean id="PropertyDrivenDiscovery" parent="shibboleth.Functions.Constant"
- c:target="#{'%{idp.authn.oidc.rp.proxyIssuer:}'.trim()}" />
-
-
-
- <!-- Spring controller to start the authentication request and recieve the response -->
- <bean id="shibboleth.authn.OIDC.externalAuthnPath" class="java.lang.String"
- c:_0="servletRelative:/Authn/OIDC/RP/authz">
- </bean>
+ c:target="#{'%{idp.authn.oidc.rp.proxyIssuer:}'.trim()}" />
+
<!-- Parent beans for indirecting into nested PRC. -->
@@ -42,7 +36,7 @@
<bean id="ParentAuthenticiationContextLookup" class="org.opensaml.messaging.context.navigate.ParentContextLookup"
c:type="net.shibboleth.idp.authn.context.AuthenticationContext" />
-
+
<!-- Action beans -->
<!-- Explicitly wrapped by a non-nested action adaptor. -->
@@ -73,7 +67,8 @@
<bean id="OIDCProviderMetadataLookup" parent="NestedWebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="INBOUND">
<constructor-arg name="messageHandler">
- <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCProviderMetadataLookupHandler" scope="prototype">
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.OIDCProviderMetadataLookupHandler"
+ scope="prototype">
<property name="providerMetadataResolver">
<ref bean="shibboleth.oidc.rp.ProviderMetadataResolver" />
</property>
@@ -82,23 +77,10 @@
</property>
</bean>
</constructor-arg>
- </bean>
-
- <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.OIDCClientMetadataLookupHandler" scope="prototype">
- <property name="clientInformationResolver">
- <ref bean="shibboleth.oidc.rp.ClientInformationResolver" />
- </property>
- <!-- <property name="clientIDLookupStrategy">
- <ref bean="shibboleth.oidc.rp.ClientIDLookupStrategy" />
- </property> -->
- </bean>
- </constructor-arg>
- </bean>
+ </bean>
+
- <bean id="shibboleth.oidc.rp.IssuerIDLookupStrategy"
+ <bean id="shibboleth.oidc.rp.IssuerIDLookupStrategy"
class="net.shibboleth.idp.plugin.authn.oidc.rp.metadata.impl.DefaultIssuerIDLookupFunction"
scope="prototype" />
@@ -112,6 +94,12 @@
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
scope="prototype" />
+ <bean id="InitializeOAuth2ClientContext" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientContext"
+ p:clientIdResolver-ref="shibboleth.oidc.rp.OAuth2ClientIdentifierResolver"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+
+
<bean id="SelectRelyingPartyConfiguration"
class="net.shibboleth.idp.profile.impl.SelectRelyingPartyConfiguration" scope="prototype"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
@@ -125,8 +113,8 @@
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"
+ <bean id="AddOIDCAuthenticationRequest" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.AddOIDCAuthenticationRequest"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
@@ -200,6 +188,12 @@
<!-- CODE flow beans -->
+ <bean id="InitializeOAuth2ClientAuthenticationContext" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.InitializeOAuth2ClientAuthenticationContext"
+ p:clientAuthenticationResolver-ref="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolver"
+ p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"/>
+
+
<bean id="ExchangeCodeForAccessToken" scope="prototype"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ExchangeCodeForAccessToken"
p:profileContextLookupStrategy-ref="shibboleth.ChildLookup.ProxyProfileRequestContext"
@@ -207,7 +201,7 @@
p:httpClient="#{getObject('shibboleth.authn.oidc.rp.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
p:httpClientSecurityParameters="#{getObject('shibboleth.authn.oidc.rp.HttpClientSecurityParameters')}"
p:httpResponseDecoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenResponseDecoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenResponseDecoder')}"
- p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultTokenRequestEncoder')}"/>
+ p:httpRequestEncoderStrategy="#{getObject('shibboleth.authn.oidc.rp.TokenRequestEncoder') ?: getObject('shibboleth.authn.oidc.rp.DefaultAuthCodeTokenResponseEncoder')}"/>
<bean id="ValidateOAuthAccessTokenResponse"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateOAuthAccessTokenResponse"
@@ -227,8 +221,8 @@
class="net.shibboleth.idp.plugin.authn.oidc.rp.decoding.impl.DefaultMapResponseDecoder"
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"/>
+ <bean id="shibboleth.authn.oidc.rp.DefaultAuthCodeTokenResponseEncoder" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.encoding.impl.NimbusAuthCodeTokenRequestEncoder"/>
<!-- Create a default object mapper. Setup should not change once injected -->
<bean id="shibboleth.authn.oidc.rp.JSONObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
@@ -338,7 +332,7 @@
<bean id="AzpClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
- p:claimName="azp" p:valueToMatchLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction">
+ p:claimName="azp" p:valueToMatchLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction">
<property name="activationCondition">
<bean id="AzpClaimExistsCondition"
class="net.shibboleth.oidc.security.jwt.claims.impl.ClaimExistsActivationCondition"
@@ -356,16 +350,14 @@
<bean id="AudienceClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
- p:audienceLookupStrategy-ref="ClientIDFromOIDCMetadataContextLookupFunction"/>
+ p:audienceLookupStrategy-ref="ClientIDFromOAuth2ClientContextFunction"/>
+
- <bean id="ClientIDFromOIDCMetadataContextLookupFunction"
- class="net.shibboleth.oidc.profile.logic.ClientIDFromOIDCMetadataContextLookupFunction">
- <property name="oIDCMetadataContextLookupStrategy">
- <bean parent="shibboleth.Functions.Compose"
- c:g-ref="OIDCMetadataContextChildLookup"
- c:f-ref="shibboleth.MessageContextLookup.Outbound" />
- </property>
- </bean>
+ <bean id="ClientIDFromOAuth2ClientContextFunction"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ClientIDFromOAuth2ClientContextFunction"
+ c:oauth2ClientContextLookupStrategy-ref="shibboleth.ChildLookup.OAuth2ClientContextFromOutbound"/>
+
+
<bean id="NonceClaimValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
@@ -385,6 +377,10 @@
<bean id="OIDCMetadataContextChildLookup"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext) }" />
+
+ <bean id="OIDCPeerEntityContextChildLookup"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext) }" />
<util:list id="IDTokenClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
<ref bean="IssuerClaimsValidator" /> <!-- TODO prevent: if it contains additional audiences not trusted by the Client. -->
@@ -491,17 +487,9 @@
<!-- OLD STUFF -->
- <!-- TODO Add user import here
- <import resource="openidconnect-authn-config.xml" /> -->
-
-
- <!-- Populate RP UI info from metadata? -->
-
-
<bean id="ValidateIDTokenSignature"
class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.ValidateIDTokenSignature" />
-
<bean id="shibboleth.authn.oidc.rp.jwt.DefaultAuthTimeActivationCondition"
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 44e1bd6..cb825fc 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
@@ -26,14 +26,13 @@
<evaluate expression="OIDCProviderMetadataLookup" />
<evaluate expression="InitializeRelyingPartyContext" />
<evaluate expression="SelectRelyingPartyConfiguration" />
- <!-- Get RP information relating to this OP -->
- <evaluate expression="OIDCClientMetadataLookup" />
<!-- <evaluate expression="PostLookupPopulateAuditContext" /> -->
<evaluate expression="InitializeOutboundMessageContext" />
+ <evaluate expression="InitializeOAuth2ClientContext" />
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="PopulateResponseTypeAndModeContext"/>
- <evaluate expression="AddAuthzRequest"/>
+ <evaluate expression="AddOIDCAuthenticationRequest"/>
<!-- <evaluate expression="PostRequestPopulateAuditContext" />
<evaluate expression="WriteAuditLog" /> -->
@@ -88,6 +87,7 @@
</decision-state>
<action-state id="CodeFlow">
+ <evaluate expression="InitializeOAuth2ClientAuthenticationContext"/>
<evaluate expression="ExchangeCodeForAccessToken"/>
<evaluate expression="ValidateOAuthAccessTokenResponse"/>
<evaluate expression="ExtractIDTokenFromTokenResponse"/>
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml
new file mode 100644
index 0000000..db1fc48
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientauthentication-resolver-system.xml
@@ -0,0 +1,33 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationProvider" lazy-init="false"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientAuthenticationContainer"
+ p:embeddedResolver-ref="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolverImpl">
+ </bean>
+
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientAuthenticationResolverImpl"
+ class="net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolverImpl"
+ p:storageService="#{getObject('shibboleth.authn.oidc.rp.StorageService') ?: getObject('DefaultMapBackedClientAuthenticationStorageService')}" />
+
+
+ <bean id="DefaultMapBackedClientAuthenticationStorageService" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MapBackedMemoryStorageServiceFactoryBean"
+ c:context="#{T(net.shibboleth.oidc.security.impl.OAuth2ClientAuthenticationResolverImpl).CONTEXT_NAME}"
+ c:map="#{getObject('shibboleth.authn.oidc.rp.ClientIdToClientAuthenticationMap')}"
+ c:id="DefaultClientIDToClientAuthenticationMapStorageService" />
+
+ <!-- Client Authentication parent bean which defaults secrets to not expire -->
+ <bean id="shibboleth.authn.oidc.rp.ClientAuthenticationDetails"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.storage.ClientAuthenticationDetails" abstract="true"
+ c:clientSecretExpiresAt="0"/>
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml
new file mode 100644
index 0000000..ac5f449
--- /dev/null
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/clientidentifier-resolver-system.xml
@@ -0,0 +1,30 @@
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierProvider" lazy-init="false"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.spring.impl.OAuth2ClientIdentifierContainer"
+ p:embeddedResolver-ref="shibboleth.oidc.rp.OAuth2ClientIdentifierResolverImpl">
+ </bean>
+
+ <bean id="shibboleth.oidc.rp.OAuth2ClientIdentifierResolverImpl"
+ class="net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolverImpl"
+ p:providerMetadataLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
+ p:storageService="#{getObject('shibboleth.authn.oidc.rp.StorageService') ?: getObject('DefaultMapBackedClientIdStorageService')}" />
+
+
+ <bean id="DefaultMapBackedClientIdStorageService" scope="prototype"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.impl.MapBackedMemoryStorageServiceFactoryBean"
+ c:context="#{T(net.shibboleth.oidc.security.impl.OAuth2ClientIdentifierResolverImpl).CONTEXT_NAME}"
+ c:map="#{getObject('shibboleth.authn.oidc.rp.IssuerToClientIdMap')}"
+ c:id="DefaultIssuerToClientIDMapStorageService" />
+
+
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
index f5a6720..cff8c0d 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/providermetadata-resolver-system.xml
@@ -32,29 +32,19 @@
<bean id="shibboleth.oidc.rp.DefaultHTTProviderConfigurationMetadataResponseHandler"
class="net.shibboleth.oidc.metadata.impl.HTTPProviderConfigurationFetchingStrategy.OIDCProviderMetadataResponseHandler" />
-
-
- <!-- Batch OIDC provider cache builder factory and template specification -->
- <bean id="batchCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilder$Builder"/>
+ <!-- Cache builder specifications -->
+ <bean id="cacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.MetadataCacheBuilder$Builder"/>
- <bean id="shibboleth.oidc.rp.BatchCacheBuilder" factory-bean="batchCacheFactory" factory-method="build"
+ <bean id="shibboleth.oidc.rp.CacheBuilder" factory-bean="cacheFactory" factory-method="build"
abstract="true"/>
-
+
<bean class="net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCacheBuilderSpec"
id="shibboleth.oidc.rp.BaseProviderBatchCacheBuilderSpec" abstract="true"
p:parsingStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataParsingStrategy"
p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataCriteriaToIdentifierStrategy"
p:sourceMetadataExpiryStrategy-ref="shibboleth.oidc.rp.DefaultODICProviderSourceMetadataExpirationTimeStrategy"
p:identifierExtractionStrategy-ref="shibboleth.oidc.rp.DefaultOIDCProviderMetadataIdentifierExtractionStrategy"/>
-
-
- <!-- Dynamic OIDC provider cache builder factory and template specification -->
-
- <bean id="dynamicCacheFactory" class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilder$Builder"/>
-
- <bean id="shibboleth.oidc.rp.DynamicCacheBuilder" factory-bean="dynamicCacheFactory" factory-method="build"
- abstract="true"/>
-
+
<bean class="net.shibboleth.oidc.metadata.cache.impl.DynamicMetadataCacheBuilderSpec"
id="shibboleth.oidc.rp.BaseProviderDynamicCacheBuilderSpec" abstract="true"
p:fetchStrategy-ref="shibboleth.oidc.rp.DefaultHTTPProviderConfigurationMetadataFetchingStrategy"
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/DefaultAuthCodeTokenRequestEncoderTest.java
similarity index 92%
rename from idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultTokenRequestEncoderTest.java
rename to idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoderTest.java
index b0b68e8..ccbaaf1 100644
--- 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/DefaultAuthCodeTokenRequestEncoderTest.java
@@ -30,15 +30,15 @@ import org.testng.annotations.Test;
import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
/** Tests for the DefaultTokenRequestEncoder.*/
-public class DefaultTokenRequestEncoderTest extends AbstractOIDCTest {
+public class DefaultAuthCodeTokenRequestEncoderTest extends AbstractOIDCTest {
/** The encoder to test.*/
- private DefaultTokenRequestEncoder encoder;
+ private DefaultAuthCodeTokenRequestEncoder encoder;
@BeforeMethod
public void setup() throws Exception {
super.setup();
- encoder = new DefaultTokenRequestEncoder();
+ encoder = new DefaultAuthCodeTokenRequestEncoder();
}
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java
new file mode 100644
index 0000000..be33b90
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoderTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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 static org.testng.Assert.assertNull;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.interfaces.RSAPrivateKey;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT;
+import com.nimbusds.oauth2.sdk.auth.Secret;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.impl.AbstractOIDCTest;
+import net.shibboleth.oidc.authn.context.OAuth2ClientAuthenticationContext;
+
+/** Tests for the DefaultTokenRequestEncoder.*/
+public class NimbusAuthCodeTokenRequestEncoderTest extends AbstractOIDCTest {
+
+ /** The encoder to test.*/
+ private NimbusAuthCodeTokenRequestEncoder encoder;
+
+ @Override
+ @BeforeMethod
+ public void setup() throws Exception {
+ super.setup();
+ encoder = new NimbusAuthCodeTokenRequestEncoder();
+ }
+
+
+ @Test
+ public void testEncoder_ClientSecretBasic_Success() throws Exception {
+
+ final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+ clientAuthContext.setClientAuthentication(
+ new ClientSecretBasic(new ClientID("test_client"), new Secret("secret")));
+ peerEntityCtx.addSubcontext(clientAuthContext);
+
+ encoder.initialize();
+ final HttpUriRequest request = encoder.apply(prc);
+ assertNotNull(request);
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ assertNotNull(request.getFirstHeader("Authorization"));
+ assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+ final 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"));
+ }
+
+ @Test
+ public void testEncoder_ClientSecretPost_Success() throws Exception {
+
+ final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+ clientAuthContext.setClientAuthentication(
+ new ClientSecretPost(new ClientID("test_client"), new Secret("secret")));
+ peerEntityCtx.addSubcontext(clientAuthContext);
+
+ encoder.initialize();
+ final HttpUriRequest request = encoder.apply(prc);
+ assertNotNull(request);
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ assertNull(request.getFirstHeader("Authorization"));
+ assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+ final String content = new String(
+ ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+ assertTrue("grant_type expected in request", content.contains("grant_type"));
+ assertTrue("client_secret expected in request", content.contains("client_secret"));
+ assertTrue("client_id expected in request", content.contains("client_id"));
+ 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"));
+ }
+
+ @Test
+ public void testEncoder_ClientSecretJWT_Success() throws Exception {
+
+ final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+ clientAuthContext.setClientAuthentication(
+ new ClientSecretJWT(new ClientID("test_client"),
+ new URI("https://rp.example.com/callback"), JWSAlgorithm.HS256,
+ new Secret("9y$B?E(H+MbQeThWmZq4t7w!z%C*F)J@")));
+ peerEntityCtx.addSubcontext(clientAuthContext);
+
+ encoder.initialize();
+ final HttpUriRequest request = encoder.apply(prc);
+ assertNotNull(request);
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ assertNull(request.getFirstHeader("Authorization"));
+ assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+ final String content = new String(
+ ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+ assertTrue("grant_type expected in request", content.contains("grant_type"));
+ assertTrue("client_assertion_type expected in request", content.contains("client_assertion_type"));
+ assertTrue("client_assertion expected in request", content.contains("client_assertion"));
+ 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"));
+ }
+
+ @Test
+ public void testEncoder_PrivateKeyJWT_Success() throws Exception {
+
+ final RSAKey key = new RSAKeyGenerator(2048)
+ .keyID("1")
+ .keyUse(KeyUse.ENCRYPTION)
+ .generate();
+
+
+ final OAuth2ClientAuthenticationContext clientAuthContext = new OAuth2ClientAuthenticationContext();
+ clientAuthContext.setClientAuthentication(
+ new PrivateKeyJWT(
+ new ClientID("test_client"),
+ new URI("https://rp.example.com/callback"),
+ JWSAlgorithm.RS256,
+ (RSAPrivateKey)key.toPrivateKey(),
+ "1",
+ null));
+ peerEntityCtx.addSubcontext(clientAuthContext);
+
+ encoder.initialize();
+ final HttpUriRequest request = encoder.apply(prc);
+ assertNotNull(request);
+ assertTrue(request instanceof HttpEntityEnclosingRequest);
+ assertNull(request.getFirstHeader("Authorization"));
+ assertNotNull(((HttpEntityEnclosingRequest)request).getEntity().getContent());
+ final String content = new String(
+ ((HttpEntityEnclosingRequest)request).getEntity().getContent().readAllBytes(), StandardCharsets.UTF_8);
+ assertTrue("grant_type expected in request", content.contains("grant_type"));
+ assertTrue("client_assertion_type expected in request", content.contains("client_assertion_type"));
+ assertTrue("client_assertion expected in request", content.contains("client_assertion"));
+ 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
index f2dede9..6c5b8e5 100644
--- 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
@@ -137,6 +137,9 @@ public abstract class AbstractOIDCTest {
/** The request context to use.*/
protected RequestContext src;
+ /** The request context to use.*/
+ protected OIDCPeerEntityContext peerEntityCtx;
+
/**
* Setup the various contexts.
*
@@ -157,6 +160,7 @@ public abstract class AbstractOIDCTest {
final MessageContext outMsgCtx = new MessageContext();
final OIDCAuthenticationRequest request = new OIDCAuthenticationRequest(new ClientID("https://rp.example.com"));
request.setState(new State("8df98fd63a53fa5b5433d6f8754bca5d.65317332"));
+ request.setRedirectURI(new URI("https://rp.example.com/callback"));
outMsgCtx.setMessage(request);
prc.setOutboundMessageContext(outMsgCtx);
@@ -166,12 +170,14 @@ public abstract class AbstractOIDCTest {
OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
outMsgCtx.addSubcontext(metadataContext);
- final OIDCPeerEntityContext peerEntitCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
+
+
+ peerEntityCtx = outMsgCtx.getSubcontext(OIDCPeerEntityContext.class,true);
final OIDCProviderMetadataContext providerCtx = new OIDCProviderMetadataContext();
providerCtx.setProviderInformation(
OIDCProviderMetadata.parse((JSONObject)parser.parse(GOOD_PROVIDER_CONFIGURATION_INFO)));
- peerEntitCtx.addSubcontext(providerCtx);
- outMsgCtx.addSubcontext(peerEntitCtx);
+ peerEntityCtx.addSubcontext(providerCtx);
+ outMsgCtx.addSubcontext(peerEntityCtx);
//Set the inbound reponse.
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 8cf421c..1550a2a 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
@@ -27,6 +27,7 @@ import static org.testng.Assert.assertTrue;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
+import java.util.Set;
import javax.annotation.Nonnull;
import javax.servlet.ServletContext;
@@ -71,7 +72,9 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.context.ExternalAuthenticationContext;
import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCAuthnContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
import net.shibboleth.idp.plugin.authn.test.flow.mock.IdPPropertyConfigurer;
import net.shibboleth.idp.session.IdPSession;
import net.shibboleth.idp.session.context.SessionContext;
@@ -96,8 +99,11 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
/** The endpoint to redirect the user-agent to.*/
@Nonnull private final String ENDPOINT_URI = "https://op.example.com/";
+ /** The issuer or OP identifier.*/
+ @Nonnull private final String ISSUER = "https://op.example.com";
+
/** The redirect to direct the user-agent to after successful authentication. */
- @Nonnull private final String REDIRECT_URI = "https://rp.example.com/callback";
+ @Nonnull private final String REDIRECT_URI = "http://localhost/Authn/OIDC/RP/callback";
/** The mock MVC entry point for testing. */
@Nonnull private MockMvc mockMvc;
@@ -125,6 +131,8 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
assertNotNull(controller);
assertNotNull(response);
assertNotNull(request);
+ controller.setRedirectUriCreationStrategy(
+ new DefaultRedirectUriCreationFunction("/Authn/OIDC/RP/callback", Set.of("http://localhost")));
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
@@ -236,6 +244,13 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
final MessageContext msgCtx = new MessageContext();
msgCtx.setMessage(request);
prc.setOutboundMessageContext(msgCtx);
+
+ final OIDCPeerEntityContext peerEntity = new OIDCPeerEntityContext();
+ peerEntity.setIdentifier(ISSUER);
+ final OAuth2ClientContext clientContext = peerEntity.getSubcontext(OAuth2ClientContext.class, true);
+
+
+ prc.getOutboundMessageContext().addSubcontext(peerEntity);
return rootPrc;
}
@@ -261,7 +276,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
/** The message encoder to be returned by this factory. */
- @Nonnull private MessageEncoder messageEncoder;
+ @Nonnull private final MessageEncoder messageEncoder;
/**
*
@@ -307,7 +322,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
private class MockRedirectEncoder extends AbstractOIDCMessageEncoder {
@Override
- public boolean test(OIDCHttpRequestMethod t) {
+ public boolean test(final OIDCHttpRequestMethod t) {
return true;
}
@@ -319,7 +334,7 @@ public class AuthorizationControllerTest extends AbstractTestNGSpringContextTest
final OIDCAuthenticationRequest outboundMessage = (OIDCAuthenticationRequest)messageContext.getMessage();
- URLBuilder urlBuilder = new URLBuilder(outboundMessage.getEndpointURI().toString());
+ final URLBuilder urlBuilder = new URLBuilder(outboundMessage.getEndpointURI().toString());
serializeAuthorizationParamsToUrl(outboundMessage, urlBuilder);
final String redirectURL = urlBuilder.buildURL();
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java
new file mode 100644
index 0000000..bc481ac
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/DefaultRedirectUriCreationFunctionTest.java
@@ -0,0 +1,169 @@
+package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Set;
+
+import org.mockito.MockitoAnnotations;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OIDCPeerEntityContext;
+
+public class DefaultRedirectUriCreationFunctionTest {
+
+ /** Static callback path from servlet request.*/
+ private static final String CALLBACK_PATH = "/Authn/OIDC/RP/callback";
+
+ /** The action to test. */
+ private DefaultRedirectUriCreationFunction strategy;
+
+ /** HTTP request mock.*/
+ private MockHttpServletRequest request;
+
+ /** Created PRC.*/
+ private ProfileRequestContext prc;
+
+ /** Created client context.*/
+ private OAuth2ClientContext clientContext;
+
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ request = new MockHttpServletRequest();
+ MockitoAnnotations.openMocks(this);
+ prc = new ProfileRequestContext();
+ prc.setOutboundMessageContext(new MessageContext());
+ final OIDCPeerEntityContext peerCtx =
+ prc.getOutboundMessageContext().getSubcontext(OIDCPeerEntityContext.class, true);
+ clientContext = peerCtx.getSubcontext(OAuth2ClientContext.class, true);
+
+ }
+
+ @Test
+ public final void testComputedRedirectAllowed() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com"));
+
+ request.addHeader("Host", "allowed.com");
+ request.setServerPort(443);
+ request.setScheme("https");
+
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect.toASCIIString(), "https://allowed.com"+CALLBACK_PATH);
+
+ }
+
+ @Test
+ public final void testComputedRedirectDisallowed() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com"));
+
+ request.addHeader("Host", "not-allowed.com");
+ request.setServerPort(443);
+ request.setScheme("https");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect, null);
+
+ }
+
+ @Test
+ public final void testComputedRedirectDisallowedNotOrigins() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Collections.emptySet());
+
+ request.addHeader("Host", "not-allowed.com");
+ request.setServerPort(443);
+ request.setScheme("https");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect, null);
+
+ }
+
+ @Test
+ public final void testComputedRedirectAllowedHTTPSCustomPort() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("https://allowed.com:8443"));
+
+ request.addHeader("Host", "allowed.com");
+ request.setServerPort(8443);
+ request.setScheme("https");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect.toASCIIString(), "https://allowed.com:8443"+CALLBACK_PATH);
+
+ }
+
+ @Test
+ public final void testComputedRedirectAllowedHTTPCustomPort() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com:8080"));
+
+ request.addHeader("Host", "allowed.com");
+ request.setServerPort(8080);
+ request.setScheme("http");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect.toASCIIString(), "http://allowed.com:8080"+CALLBACK_PATH);
+
+ }
+
+ @Test
+ public final void testComputedRedirectAllowedNullPort() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+
+ request.addHeader("Host", "allowed.com");
+ request.setServerPort(-1);
+ request.setScheme("http");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect.toASCIIString(), "http://allowed.com"+CALLBACK_PATH);
+
+ }
+
+ @Test
+ public final void testComputedRedirectDisallowedOnPort() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+
+ request.addHeader("Host", "allowed.com");
+ request.setServerPort(443);
+ request.setScheme("https");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect, null);
+
+ }
+
+ @Test
+ public final void testPreregisteredURL() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+
+ //request is irrelevant
+ request.addHeader("Host", "notused.com");
+ request.setServerPort(443);
+ request.setScheme("https");
+ clientContext.setRedirectUriOverride(new URI("https://registered.com"+CALLBACK_PATH));
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect.toASCIIString(), "https://registered.com"+CALLBACK_PATH);
+
+ }
+
+ @Test
+ public final void testComputedRedirectBadHostname() throws Exception {
+
+ strategy = new DefaultRedirectUriCreationFunction(CALLBACK_PATH, Set.of("http://allowed.com"));
+
+ request.addHeader("Host", "<script>inject</script>");
+ request.setServerPort(80);
+ request.setScheme("http");
+ final URI redirect = strategy.apply(request, prc);
+ assertEquals(redirect, null);
+
+ }
+
+}
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 1f5537f..1806afd 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
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
@@ -75,23 +76,19 @@ import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
import com.nimbusds.openid.connect.sdk.Nonce;
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.context.SubjectCanonicalizationContext;
import net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl;
+import net.shibboleth.idp.plugin.authn.oidc.rp.context.OAuth2ClientContext;
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.test.flow.AbstractAuthnXmlFlowExecutionTests;
import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.idp.relyingparty.RelyingPartyConfiguration;
-import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
@@ -107,6 +104,8 @@ import okhttp3.tls.HeldCertificate;
public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
private static final String OP_ISSUER_ID = "https://localhost:9918";
+
+ private final String RP_ALLOWED_ORIGINS = "https://localhost";
/**
* Example of good provider metadata. Endpoints are localhost to support the
@@ -170,22 +169,7 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
+ "\"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n"
+ "]\n"
+ "}";
-
- @Nonnull private final String CLIENT_METADATA = "[\n"
- + " {\n"
- + " \"issuer\": \""+OP_ISSUER_ID+"\",\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"
- + " \"client_secret\": \"demo_rp_secret\",\n"
- + " \"response_types\": [\n"
- + " \"id_token\",\n"
- + " \"id_token token\"\n"
- + " ]\n"
- + " }"
- + "]";
+
/** Mock JSON Object response from the UserInfo endpoint.*/
@Nonnull @NotEmpty
@@ -456,20 +440,14 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
return request;
}
- /**
- * Create a client metadata context
- *
- * @return the context.
- *
- * @throws Exception on error.
- */
- private OIDCMetadataContext createClientMedataContext() throws Exception {
- final JSONParser parser = new JSONParser(JSONParser.MODE_JSON_SIMPLE);
- final OIDCMetadataContext metadataContext = new OIDCMetadataContext();
- metadataContext.setClientInformation(
- OIDCClientInformation.parse((JSONObject)((JSONArray)parser.parse(CLIENT_METADATA)).get(0)));
- return metadataContext;
+ private OAuth2ClientContext createOAuth2ClientContext(@Nonnull final String clientId,
+ @Nullable final URI redirectOverride) {
+ final OAuth2ClientContext context = new OAuth2ClientContext();
+ context.setClientId(clientId);
+ context.setRedirectUriOverride(redirectOverride);
+ return context;
}
+
/**
* Create a response mode and type context.
@@ -513,7 +491,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final Map<String,String> mockProperties = Map.of(
"idp.service.clientinfo.failFast","false",
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+ "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
setMockProperties(mockProperties);
@@ -552,7 +531,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final Map<String,String> mockProperties = Map.of(
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+ "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
setMockProperties(mockProperties);
@@ -591,8 +571,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
outMsgCtx.setMessage(createAuthenticationRequest());
outMsgCtx.addSubcontext(createPeerContext());
outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
- outMsgCtx.addSubcontext(createClientMedataContext());
nestPrc.setOutboundMessageContext(outMsgCtx);
+ outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
// Setup inbound context.
final MessageContext inMsgCtx = new MessageContext();
@@ -669,8 +649,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
outMsgCtx.setMessage(createAuthenticationRequest());
outMsgCtx.addSubcontext(createPeerContext());
outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
- outMsgCtx.addSubcontext(createClientMedataContext());
- nestPrc.setOutboundMessageContext(outMsgCtx);
+ nestPrc.setOutboundMessageContext(outMsgCtx);
+ outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
// Setup inbound context.
final MessageContext inMsgCtx = new MessageContext();
@@ -711,7 +691,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final Map<String,String> mockProperties = Map.of(
"idp.service.clientinfo.failFast","false",
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+ "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
setMockProperties(mockProperties);
@@ -748,8 +729,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
outMsgCtx.setMessage(createAuthenticationRequest());
outMsgCtx.addSubcontext(createPeerContext());
outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
- outMsgCtx.addSubcontext(createClientMedataContext());
nestPrc.setOutboundMessageContext(outMsgCtx);
+ outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
// Setup inbound context.
final MessageContext inMsgCtx = new MessageContext();
@@ -791,7 +772,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final Map<String,String> mockProperties = Map.of(
"idp.service.clientinfo.failFast","false",
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+ "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
setMockProperties(mockProperties);
@@ -828,8 +810,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
outMsgCtx.setMessage(createAuthenticationRequest());
outMsgCtx.addSubcontext(createPeerContext());
outMsgCtx.addSubcontext(createResponseTypeAndModeContext());
- outMsgCtx.addSubcontext(createClientMedataContext());
nestPrc.setOutboundMessageContext(outMsgCtx);
+ outMsgCtx.getSubcontext(OIDCPeerEntityContext.class).addSubcontext(createOAuth2ClientContext("demo_rp",null));
// Setup inbound context.
final MessageContext inMsgCtx = new MessageContext();
@@ -878,7 +860,8 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
final Map<String,String> mockProperties = Map.of(
"idp.service.clientinfo.failFast","false",
"idp.entityID", "http://idp.example.com/",
- "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID);
+ "idp.authn.oidc.rp.proxyIssuer",OP_ISSUER_ID,
+ "idp.oidc.rp.redirecturl.allowedOrigins", RP_ALLOWED_ORIGINS);
setMockProperties(mockProperties);
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml
new file mode 100644
index 0000000..667b19b
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-authentication.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy">
+
+
+ <!--
+ Map clients to appropriate client authentication
+ -->
+
+ <util:map id="shibboleth.authn.oidc.rp.ClientIdToClientAuthenticationMap">
+ <entry key="mytestclient">
+ <bean parent="shibboleth.authn.oidc.rp.ClientAuthenticationDetails" c:clientSecret="myclientsecret"
+ c:tokenEndpointAuthMethod="client_secret_basic" />
+ </entry>
+ <entry key="demo_rp">
+ <bean parent="shibboleth.authn.oidc.rp.ClientAuthenticationDetails" c:clientSecret="myclientsecret"
+ c:tokenEndpointAuthMethod="client_secret_basic" />
+ </entry>
+ </util:map>
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml
new file mode 100644
index 0000000..d684f62
--- /dev/null
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-client-registration-clientid.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <!--
+ Map issuers to appropriate clients
+ -->
+
+ <util:map id="shibboleth.authn.oidc.rp.IssuerToClientIdMap">
+ <entry key="https://localhost:9918" value="mytestclient" />
+ </util:map>
+
+
+</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
index c789a44..4f5bd93 100644
--- a/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
+++ b/idp-oidc-rp-impl/src/test/resources/conf/authn/oidc-providermetadata-resolvers.xml
@@ -19,7 +19,7 @@
<bean id="ExampleHTTPResolver" parent="shibboleth.oidc.rp.OIDCProviderMetadataResolver">
<constructor-arg>
- <bean parent="shibboleth.oidc.rp.DynamicCacheBuilder">
+ <bean parent="shibboleth.oidc.rp.CacheBuilder">
<constructor-arg>
<bean p:cacheId="ExampleHTTPDynamicResolver" parent="shibboleth.oidc.rp.BaseProviderDynamicCacheBuilderSpec"
p:minCacheDuration="PT1M"
diff --git a/pom.xml b/pom.xml
index 2f965b9..05904f9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,8 +20,6 @@
<opensaml.groupId>org.opensaml</opensaml.groupId>
<opensaml.version>4.2.0-SNAPSHOT</opensaml.version>
<oidc.common.version>1.2.0-SNAPSHOT</oidc.common.version>
- <!-- This is tmp until certain classes move over to commons -->
- <oidc.op.version>3.1.0-SNAPSHOT</oidc.op.version>
<checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
</properties>
@@ -97,17 +95,6 @@
<artifactId>idp-plugin-oidc-rp-impl</artifactId>
<version>${project.version}</version>
</dependency>
- <!-- Tmp OP deps until things move to commons -->
- <dependency>
- <groupId>net.shibboleth.idp.plugin.oidc</groupId>
- <artifactId>idp-plugin-oidc-op-api</artifactId>
- <version>${oidc.op.version}</version>
- </dependency>
- <dependency>
- <groupId>net.shibboleth.idp.plugin.oidc</groupId>
- <artifactId>idp-plugin-oidc-op-impl</artifactId>
- <version>${oidc.op.version}</version>
- </dependency>
<!-- OIDC Common BOM when importing OIDC dependencies -->
<dependency>
<groupId>net.shibboleth.oidc</groupId>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list