[java-idp-oidc] branch main updated: JOIDC-64 - Refactor client authn on OAuth2 endpoints into a login flow
Scott Cantor
cantor.2 at osu.edu
Tue Dec 14 21:51:43 UTC 2021
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=cfdb88ffdf11a0c76687dbcaa833fdd55b69e3ce
The following commit(s) were added to refs/heads/main by this push:
new cfdb88ff JOIDC-64 - Refactor client authn on OAuth2 endpoints into a login flow
cfdb88ff is described below
commit cfdb88ffdf11a0c76687dbcaa833fdd55b69e3ce
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 14 16:51:37 2021 -0500
JOIDC-64 - Refactor client authn on OAuth2 endpoints into a login flow
https://shibboleth.atlassian.net/browse/JOIDC-64
Refactor extraction and method validation steps.
---
.../context/OAuth2ClientAuthenticationContext.java | 64 +++++++
.../plugin/oidc/op/authn/context/package-info.java | 22 +++
.../ExtractClientAuthenticationFromRequest.java | 142 +++++++++++++++
.../impl/ValidateClientAuthenticationType.java | 170 ++++++++++++++++++
.../plugin/oidc/op/authn/impl/package-info.java | 22 +++
...ExtractClientAuthenticationFromRequestTest.java | 196 +++++++++++++++++++++
.../impl/ValidateClientAuthenticationTypeTest.java | 181 +++++++++++++++++++
7 files changed, 797 insertions(+)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/OAuth2ClientAuthenticationContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/OAuth2ClientAuthenticationContext.java
new file mode 100644
index 00000000..9c7587d8
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/OAuth2ClientAuthenticationContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.oidc.op.authn.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+
+/**
+ * A context containing data about OAuth 2.0 client authentication.
+ *
+ * <p>Currently implemented using Nimbus APIs.</p>
+ *
+ * @parent {@link AuthenticationContext}
+ * @added During an OAuth 2.0 authentication attempt
+ */
+public final class OAuth2ClientAuthenticationContext extends BaseContext {
+
+ /** Client authentication abstraction. */
+ @Nullable private ClientAuthentication clientAuthentication;
+
+ /**
+ * Get the OAuth 2 client authentication credentials.
+ *
+ * @return client authentication credentials
+ */
+ @Nullable public ClientAuthentication getClientAuthentication() {
+ return clientAuthentication;
+ }
+
+ /**
+ * Set the OAuth 2 client authentication credentials.
+ *
+ * @param creds client authentication credentials
+ *
+ * @return this context
+ */
+ @Nonnull public OAuth2ClientAuthenticationContext setClientAuthentication(
+ @Nullable final ClientAuthentication creds) {
+ clientAuthentication = creds;
+ return this;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/package-info.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/package-info.java
new file mode 100644
index 00000000..029f6a85
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/context/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Contexts supporting OIDC/OAuth client authentication.
+ */
+package net.shibboleth.idp.plugin.oidc.op.authn.context;
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequest.java
new file mode 100644
index 00000000..ba846fa5
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.oidc.op.authn.impl;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.AbstractOptionallyAuthenticatedRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic;
+import com.nimbusds.oauth2.sdk.auth.ClientSecretPost;
+import com.nimbusds.oauth2.sdk.auth.TLSClientAuthentication;
+
+import net.shibboleth.idp.authn.AbstractExtractionAction;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.CertificateContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
+import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticationContext;
+
+/**
+ * Extracts OAuth 2 client authentication details from a request and stores them in an
+ * {@link OAuth2ClientAuthenticationContext} beneath the {@link AuthenticationContext} for subsequent
+ * validation.
+ *
+ * <p>Depending on the form of authentication, additional child contexts may be created to store
+ * extracted credentials, and they may undergo configured transformations. For example, password-based
+ * methods will result in a {@link UsernamePasswordContext}, certificate-based in an {@link CertificateContext},
+ * etc.</p>
+ *
+ * @pre ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null
+ * @post AuthenticationContext.getSubcontext(OAuth2ClientAuthenticationContext.class) ! null
+ * along with other contexts as appropriate
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link AuthnEventIds#NO_CREDENTIALS}
+ */
+public class ExtractClientAuthenticationFromRequest extends AbstractExtractionAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ExtractClientAuthenticationFromRequest.class);
+
+ /** Message to extract credentials from. */
+ @Nullable private AbstractOptionallyAuthenticatedRequest request;
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ if (profileRequestContext.getInboundMessageContext() != null) {
+ final Object msg = profileRequestContext.getInboundMessageContext().getMessage();
+ if (msg instanceof AbstractOptionallyAuthenticatedRequest) {
+ request = (AbstractOptionallyAuthenticatedRequest) msg;
+ return true;
+ }
+ }
+
+ log.warn("{} Inbound message missing or of incorrect type", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+// Checkstyle: CyclomaticComplexity OFF
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ final ClientAuthentication clientAuthentication = request.getClientAuthentication();
+ if (clientAuthentication == null) {
+ log.debug("{} No OAuth client credentials in request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ return;
+ }
+
+ final OAuth2ClientAuthenticationContext ctx =
+ authenticationContext.getSubcontext(OAuth2ClientAuthenticationContext.class, true);
+ ctx.setClientAuthentication(clientAuthentication);
+
+ // Note the Nimbus APIs appear to prevent the client ID or secret from being null.
+
+ if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientAuthentication.getMethod())) {
+ final ClientSecretBasic basic = (ClientSecretBasic) clientAuthentication;
+ if (basic.getClientID() != null && basic.getClientSecret() != null) {
+ final UsernamePasswordContext upContext = new UsernamePasswordContext();
+ upContext.setUsername(applyTransforms(basic.getClientID().getValue()))
+ .setPassword(basic.getClientSecret().getValue());
+ authenticationContext.addSubcontext(upContext, true);
+ } else {
+ log.warn("{} No OAuth client credentials in basic-auth request?", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ }
+ } else if (ClientAuthenticationMethod.CLIENT_SECRET_POST.equals(clientAuthentication.getMethod())) {
+ final ClientSecretPost post = (ClientSecretPost) clientAuthentication;
+ if (post.getClientID() != null && post.getClientSecret() != null) {
+ final UsernamePasswordContext upContext = new UsernamePasswordContext();
+ upContext.setUsername(applyTransforms(post.getClientID().getValue()))
+ .setPassword(post.getClientSecret().getValue());
+ authenticationContext.addSubcontext(upContext, true);
+ } else {
+ log.warn("{} No OAuth client credentials in POST request?", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+ }
+ } else if (ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getMethod()) ||
+ ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.equals(clientAuthentication.getMethod())) {
+ final TLSClientAuthentication tls = (TLSClientAuthentication) clientAuthentication;
+ if (tls.getClientX509Certificate() != null) {
+ final CertificateContext certContext = new CertificateContext();
+ certContext.setCertificate(tls.getClientX509Certificate());
+ authenticationContext.addSubcontext(certContext, true);
+ }
+ }
+ }
+// Checkstyle: CyclomaticComplexity ON
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationType.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationType.java
new file mode 100644
index 00000000..e7006960
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationType.java
@@ -0,0 +1,170 @@
+/*
+ * 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.oidc.op.authn.impl;
+
+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.InboundMessageContextLookup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCMetadataContext;
+import net.shibboleth.oidc.profile.config.navigate.TokenEndpointAuthMethodLookupFunction;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Validates the client authentication type with the token_endpoint_auth_method stored in the client's metadata
+ * and the profile configuration.
+ *
+ * @pre {@link OIDCMetadataContext} is available
+ * @pre AuthenticationContext.getSubcontext(OAuth2ClientAuthenticationContext.class) != null
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#ACCESS_DENIED}
+ */
+public class ValidateClientAuthenticationType extends AbstractAuthenticationAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ValidateClientAuthenticationType.class);
+
+ /** Strategy that will return {@link OIDCMetadataContext}. */
+ @Nonnull private Function<ProfileRequestContext,OIDCMetadataContext> oidcMetadataContextLookupStrategy;
+
+ /** Strategy to obtain enabled token endpoint authentication methods. */
+ @Nullable private Function<ProfileRequestContext,Set<ClientAuthenticationMethod>>
+ tokenEndpointAuthMethodsLookupStrategy;
+
+ /** The attached OIDC metadata context. */
+ @Nullable private OIDCMetadataContext oidcMetadataContext;
+
+ /** The extracted client authentication information. */
+ @Nullable private ClientAuthentication clientAuthentication;
+
+ /** Enabled client authn methods. */
+ @Nullable @NonnullElements private Set<ClientAuthenticationMethod> enabledMethods;
+
+ /**
+ * Constructor.
+ */
+ public ValidateClientAuthenticationType() {
+ oidcMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCMetadataContext.class).compose(
+ new InboundMessageContextLookup());
+ tokenEndpointAuthMethodsLookupStrategy = new TokenEndpointAuthMethodLookupFunction();
+ }
+
+ /**
+ * Set the strategy used to return the {@link OIDCMetadataContext}.
+ *
+ * @param strategy The lookup strategy.
+ */
+ public void setOIDCMetadataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ oidcMetadataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "OIDCMetadataContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set strategy to obtain enabled token endpoint authentication methods.
+ * @param strategy What to set.
+ */
+ public void setTokenEndpointAuthMethodsLookupStrategy(@Nonnull final Function<ProfileRequestContext,
+ Set<ClientAuthenticationMethod>> strategy) {
+ tokenEndpointAuthMethodsLookupStrategy = Constraint.isNotNull(strategy,
+ "Strategy to obtain enabled token endpoint authentication methods cannot be null");
+
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+ if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+ return false;
+ }
+
+ final OAuth2ClientAuthenticationContext oauth2Ctx =
+ authenticationContext.getSubcontext(OAuth2ClientAuthenticationContext.class);
+ if (oauth2Ctx != null) {
+ clientAuthentication = oauth2Ctx.getClientAuthentication();
+ }
+
+ oidcMetadataContext = oidcMetadataContextLookupStrategy.apply(profileRequestContext);
+ if (oidcMetadataContext == null) {
+ log.warn("{} OICDMetadataContext is missing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return false;
+ }
+
+ enabledMethods = tokenEndpointAuthMethodsLookupStrategy.apply(profileRequestContext);
+ if (enabledMethods == null) {
+ enabledMethods = Collections.emptySet();
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final AuthenticationContext authenticationContext) {
+
+ // Pull the client's registered authn method, or default to client_secret_basic.
+ // The enabledMethods member contains the methods authorized in the configuration as a whole.
+ final OIDCClientInformation clientInformation = oidcMetadataContext.getClientInformation();
+ final OIDCClientMetadata clientMetadata = clientInformation.getOIDCMetadata();
+ final ClientAuthenticationMethod registeredMethod = clientMetadata.getTokenEndpointAuthMethod() != null ?
+ clientMetadata.getTokenEndpointAuthMethod() : ClientAuthenticationMethod.CLIENT_SECRET_BASIC;
+
+ // Did the client use what it registered and is that still allowed?
+
+ final ClientAuthenticationMethod used =
+ clientAuthentication != null ? clientAuthentication.getMethod() : ClientAuthenticationMethod.NONE;
+ if (!registeredMethod.equals(used)) {
+ log.warn("{} Client '{}' registered {} but attempted {}", getLogPrefix(),
+ clientAuthentication.getClientID(), registeredMethod, used);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ } else if (!enabledMethods.contains(registeredMethod)) {
+ log.warn("{} Requested method {} is not enabled in profile configuration", getLogPrefix(),
+ registeredMethod);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+ return;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/package-info.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/package-info.java
new file mode 100644
index 00000000..b742f82b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Implementation classes supporting OIDC/OAuth client authentication.
+ */
+package net.shibboleth.idp.plugin.oidc.op.authn.impl;
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequestTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequestTest.java
new file mode 100644
index 00000000..6c7a9e7e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ExtractClientAuthenticationFromRequestTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.oidc.op.authn.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+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.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
+import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link ExtractClientAuthenticationFromRequest}.
+ */
+public class ExtractClientAuthenticationFromRequestTest {
+
+ private ClientID clientId;
+ private Secret clientSecret;
+
+ private URI endpointUri;
+
+ private RSAPrivateKey rsaPrivateKey;
+ private RSAPublicKey rsaPublicKey;
+
+ private ExtractClientAuthenticationFromRequest action;
+
+ private RequestContext rc;
+ private ProfileRequestContext prc;
+
+ @BeforeClass
+ public void initKeys() throws NoSuchAlgorithmException {
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ final KeyPair keyPair = keyGen.genKeyPair();
+ rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+ rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+ }
+
+ @BeforeMethod
+ public void init() throws URISyntaxException, ComponentInitializationException {
+ clientId = new ClientID("mockId");
+ clientSecret = new Secret("secret1234567890secret1234567890secret1234567890");
+ endpointUri = new URI("https://mock.example.org/");
+
+ action = new ExtractClientAuthenticationFromRequest();
+ action.initialize();
+ }
+
+ protected void initializeRequestCtx(final ClientAuthenticationMethod method)
+ throws JOSEException, ComponentInitializationException {
+ final ClientAuthentication clientAuth;
+ if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+ clientAuth = new ClientSecretBasic(clientId, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
+ clientAuth = new ClientSecretPost(clientId, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
+ clientAuth = new ClientSecretJWT(clientId, endpointUri, JWSAlgorithm.HS256, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
+ clientAuth = new PrivateKeyJWT(clientId, endpointUri, JWSAlgorithm.RS256, rsaPrivateKey, null, null);
+ } else {
+ clientAuth = null;
+ }
+ final AuthorizationGrant authzGrant = new AuthorizationCodeGrant(new AuthorizationCode(), null);
+
+ rc = new RequestContextBuilder()
+ .setInboundMessage(new TokenRequest(null, clientAuth, authzGrant))
+ .buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(rc);
+ prc.addSubcontext(new AuthenticationContext());
+ }
+
+ @Test
+ public void testNoAuthnContext() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ prc.removeSubcontext(AuthenticationContext.class);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertEvent(e, AuthnEventIds.INVALID_AUTHN_CTX);
+ }
+
+ @Test
+ public void testBasic() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+
+ final OAuth2ClientAuthenticationContext oauth =
+ prc.getSubcontext(AuthenticationContext.class).getSubcontext(OAuth2ClientAuthenticationContext.class);
+ Assert.assertNotNull(oauth);
+ Assert.assertEquals(oauth.getClientAuthentication().getClientID(), clientId);
+ Assert.assertEquals(
+ oauth.getClientAuthentication().getMethod(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ final UsernamePasswordContext up = oauth.getParent().getSubcontext(UsernamePasswordContext.class);
+ Assert.assertNotNull(up);
+ Assert.assertEquals(up.getUsername(), clientId.getValue());
+ Assert.assertEquals(up.getPassword(), clientSecret.getValue());
+ }
+
+ @Test
+ public void testPost() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_POST);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+
+ final OAuth2ClientAuthenticationContext oauth =
+ prc.getSubcontext(AuthenticationContext.class).getSubcontext(OAuth2ClientAuthenticationContext.class);
+ Assert.assertNotNull(oauth);
+ Assert.assertEquals(oauth.getClientAuthentication().getClientID(), clientId);
+ Assert.assertEquals(
+ oauth.getClientAuthentication().getMethod(), ClientAuthenticationMethod.CLIENT_SECRET_POST);
+ final UsernamePasswordContext up = oauth.getParent().getSubcontext(UsernamePasswordContext.class);
+ Assert.assertNotNull(up);
+ Assert.assertEquals(up.getUsername(), clientId.getValue());
+ Assert.assertEquals(up.getPassword(), clientSecret.getValue());
+ }
+
+ @Test
+ public void testSecretJwt() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+
+ final OAuth2ClientAuthenticationContext oauth =
+ prc.getSubcontext(AuthenticationContext.class).getSubcontext(OAuth2ClientAuthenticationContext.class);
+ Assert.assertNotNull(oauth);
+ Assert.assertEquals(oauth.getClientAuthentication().getClientID(), clientId);
+ Assert.assertEquals(
+ oauth.getClientAuthentication().getMethod(), ClientAuthenticationMethod.CLIENT_SECRET_JWT);
+ final UsernamePasswordContext up = oauth.getParent().getSubcontext(UsernamePasswordContext.class);
+ Assert.assertNull(up);
+ }
+
+ @Test
+ public void testPrivateKeyJwt() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+
+ final OAuth2ClientAuthenticationContext oauth =
+ prc.getSubcontext(AuthenticationContext.class).getSubcontext(OAuth2ClientAuthenticationContext.class);
+ Assert.assertNotNull(oauth);
+ Assert.assertEquals(oauth.getClientAuthentication().getClientID(), clientId);
+ Assert.assertEquals(
+ oauth.getClientAuthentication().getMethod(), ClientAuthenticationMethod.PRIVATE_KEY_JWT);
+ final UsernamePasswordContext up = oauth.getParent().getSubcontext(UsernamePasswordContext.class);
+ Assert.assertNull(up);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationTypeTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationTypeTest.java
new file mode 100644
index 00000000..7117eb7d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/authn/impl/ValidateClientAuthenticationTypeTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.oidc.op.authn.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Set;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+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 com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.oidc.op.authn.context.OAuth2ClientAuthenticationContext;
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCMetadataContext;
+import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.idp.profile.testing.RequestContextBuilder;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * Unit tests for {@link ValidateClientAuthenticationType}.
+ */
+public class ValidateClientAuthenticationTypeTest {
+
+ private ClientID clientId;
+ private Secret clientSecret;
+
+ private URI endpointUri;
+
+ private RSAPrivateKey rsaPrivateKey;
+ private RSAPublicKey rsaPublicKey;
+
+ private ValidateClientAuthenticationType action;
+
+ private RequestContext rc;
+ private ProfileRequestContext prc;
+
+ private Set<ClientAuthenticationMethod> enabledMethods;
+
+ @BeforeClass
+ public void initKeys() throws NoSuchAlgorithmException {
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ final KeyPair keyPair = keyGen.genKeyPair();
+ rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+ rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+ }
+
+ @BeforeMethod
+ public void init() throws URISyntaxException, ComponentInitializationException {
+ clientId = new ClientID("mockId");
+ clientSecret = new Secret("secret1234567890secret1234567890secret1234567890");
+ endpointUri = new URI("https://mock.example.org/");
+ enabledMethods = Collections.emptySet();
+
+ action = new ValidateClientAuthenticationType();
+ action.setTokenEndpointAuthMethodsLookupStrategy(p -> {
+ return enabledMethods;
+ }
+ );
+ action.initialize();
+ }
+
+ protected void initializeRequestCtx(final ClientAuthenticationMethod method,
+ final ClientAuthenticationMethod storedMethod) throws JOSEException, ComponentInitializationException {
+ final ClientAuthentication clientAuth;
+ if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)) {
+ clientAuth = new ClientSecretBasic(clientId, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_POST)) {
+ clientAuth = new ClientSecretPost(clientId, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.CLIENT_SECRET_JWT)) {
+ clientAuth = new ClientSecretJWT(clientId, endpointUri, JWSAlgorithm.HS256, clientSecret);
+ } else if (method.equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
+ clientAuth = new PrivateKeyJWT(clientId, endpointUri, JWSAlgorithm.RS256, rsaPrivateKey, null, null);
+ } else {
+ clientAuth = null;
+ }
+ rc = new RequestContextBuilder().setInboundMessage(null).buildRequestContext();
+ prc = new WebflowRequestContextProfileRequestContextLookup().apply(rc);
+ prc.addSubcontext(new AuthenticationContext()).addSubcontext(
+ new OAuth2ClientAuthenticationContext().setClientAuthentication(clientAuth));
+
+ final OIDCMetadataContext oidcContext = new OIDCMetadataContext();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setTokenEndpointAuthMethod(storedMethod);
+ // we're not actually validating the secret so it doesn't matter
+ final Secret secret = new Secret("WRONG1234567890secret1234567890secret1234567890");
+ final OIDCClientInformation clientInformation =
+ new OIDCClientInformation(clientId, new Date(), metadata, secret);
+ oidcContext.setClientInformation(clientInformation);
+ prc.getInboundMessageContext().addSubcontext(oidcContext);
+ }
+
+ @Test
+ public void testNoAuthnContext() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
+ ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ prc.removeSubcontext(AuthenticationContext.class);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertEvent(e, AuthnEventIds.INVALID_AUTHN_CTX);
+ }
+
+ @Test
+ public void testNoneDisabled() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.NONE,
+ ClientAuthenticationMethod.NONE);
+ prc.getSubcontext(AuthenticationContext.class).removeSubcontext(OAuth2ClientAuthenticationContext.class);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertEvent(e, EventIds.ACCESS_DENIED);
+ }
+
+ @Test
+ public void testNoneEnabled() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.NONE,
+ ClientAuthenticationMethod.NONE);
+ enabledMethods = Collections.singleton(ClientAuthenticationMethod.NONE);
+ prc.getSubcontext(AuthenticationContext.class).removeSubcontext(OAuth2ClientAuthenticationContext.class);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+ }
+
+ @Test
+ public void testBasicDisabled() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.NONE,
+ ClientAuthenticationMethod.NONE);
+ enabledMethods = Collections.singleton(ClientAuthenticationMethod.CLIENT_SECRET_POST);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertEvent(e, EventIds.ACCESS_DENIED);
+ }
+
+ @Test
+ public void testBasicEnabled() throws Exception {
+ initializeRequestCtx(ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
+ ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ enabledMethods = Collections.singleton(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
+ final Event e = action.execute(rc);
+ ActionTestingSupport.assertProceedEvent(e);
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list