[java-idp-oidc] branch main updated: JOIDC-11 - Support for client_credentials grant

Scott Cantor cantor.2 at osu.edu
Thu Feb 10 16:55:27 UTC 2022


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=3af70bb1cdbe5f463e45f3bbaf5f409b654a7461

The following commit(s) were added to refs/heads/main by this push:
     new 3af70bb1 JOIDC-11 - Support for client_credentials grant
3af70bb1 is described below

commit 3af70bb1cdbe5f463e45f3bbaf5f409b654a7461
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Feb 10 11:55:24 2022 -0500

    JOIDC-11 - Support for client_credentials grant
    
    https://shibboleth.atlassian.net/browse/JOIDC-11
    
    Rework flow to split off profile settings for token audience.
---
 .../profile/impl/AbstractEncryptTokenAction.java   |   2 +-
 .../profile/impl/EnforceAudienceConfiguration.java | 183 ---------------------
 .../op/oauth2/profile/impl/ValidateAudience.java   |  42 ++++-
 .../idp/flows/oidc/token/token-beans.xml           |   6 +-
 .../shibboleth/idp/flows/oidc/token/token-flow.xml |   7 -
 .../idp/service/relying-party/postconfig.xml       |  63 +++++--
 .../oidc/op/profile/flow/AbstractOidcFlowTest.java |  12 +-
 .../flow/ClientCredentialsTokenFlowTest.java       |  86 +++++-----
 .../src/test/resources/conf/relying-party.xml      |  13 +-
 9 files changed, 149 insertions(+), 265 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
index 1b077f13..98a0486a 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/AbstractEncryptTokenAction.java
@@ -128,7 +128,7 @@ public abstract class AbstractEncryptTokenAction extends AbstractOIDCResponseAct
         final EncryptionMethod encEnc = EncryptionMethod.parse(params.getDataEncryptionAlgorithm());
         final String kid = CredentialConversionUtil.resolveKid(credential);
 
-        log.debug("{} encrypting with key {} and params alg: {} enc: {}", getLogPrefix(), kid, encAlg.getName(),
+        log.debug("{} Encrypting with kid {} and params alg: {} enc: {}", getLogPrefix(), kid, encAlg.getName(),
                 encEnc.getName());
 
         final JWEObject jweObject =
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java
deleted file mode 100644
index ee321b3b..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/EnforceAudienceConfiguration.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
-
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-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.ProxiedRequesterContext;
-import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.idp.profile.IdPEventIds;
-import net.shibboleth.idp.profile.context.RelyingPartyContext;
-import net.shibboleth.oidc.profile.config.logic.AllowUntrustedAudiencePredicate;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/**
- * Profile action that evaluates the resolved configuration settings for the audience of a
- * token request.
- * 
- * <p>This action recognizes the duality of a token request whereby the original client and
- * the primary recipient are both relying parties of the system at different stages of the
- * flow. Either or both of them may be unverified, but enforcement of this for the audience
- * depends on a profile setting applied to the client's original profile configuration.</p>
- * 
- * <p>In addition, this action establishes the audience of a token request as a proxied
- * requester for the purposes of attribute filtering. While it is arguably more correct
- * in many cases to think of the audience as the requester, this is complicated by OIDC
- * and four-legged scenarios in which either no audience exists or both the client and
- * audience may be considered requesters. Treating the audience as proxied is done for
- * consistency across all those use cases.</p> 
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link IdPEventIds#INVALID_RELYING_PARTY_CTX}
- * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
- * 
- * @since 3.1.0
- */
-public class EnforceAudienceConfiguration extends AbstractProfileAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(EnforceAudienceConfiguration.class);
-
-    /** Strategy that will return the requester's {@link RelyingPartyContext}. */
-    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> requesterContextLookupStrategy;
-
-    /** Strategy that will return the audience's {@link RelyingPartyContext}. */
-    @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> audienceContextLookupStrategy;
-
-    /** Predicate that determines whether to allow an unverified audience. */
-    @Nonnull private Predicate<ProfileRequestContext> allowUntrustedAudienceCondition;
-    
-    /** Original context. */
-    @Nullable private RelyingPartyContext reqContext;
-
-    /** New context. */
-    @Nullable private RelyingPartyContext audContext;
-
-    /** Constructor. */
-    public EnforceAudienceConfiguration() {
-        requesterContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
-        audienceContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class).compose(
-                new OutboundMessageContextLookup());
-        allowUntrustedAudienceCondition = new AllowUntrustedAudiencePredicate();
-    }
-    
-    /**
-     * Set the strategy used to return the original requester's {@link RelyingPartyContext}.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setRequesterContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        requesterContextLookupStrategy =
-                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the strategy used to return the audience's {@link RelyingPartyContext}.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setAudienceContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-
-        audienceContextLookupStrategy =
-                Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the condition used to establish whether to allow an unverified audience.
-     * 
-     * @param condition condition to set
-     */
-    public void setUntrustedAudienceCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        allowUntrustedAudienceCondition = Constraint.isNotNull(condition,
-                "Untrusted audience condition cannot be null");
-    }
-
-    /** {@inheritDoc}. */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        
-        reqContext = requesterContextLookupStrategy.apply(profileRequestContext);
-        audContext = audienceContextLookupStrategy.apply(profileRequestContext);
-        if (reqContext == null ||audContext == null) {
-            log.error("{} Requester or audience RelyingPartyContext missing", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_RELYING_PARTY_CTX);
-            return false;
-        }
-        
-        return true;
-    }
-
-    /** {@inheritDoc}. */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        if (audContext.isVerified() && audContext.getProfileConfig() == null) {
-            log.warn("{} Token profile not enabled for verified audience: {}", getLogPrefix(),
-                    audContext.getRelyingPartyId());
-            ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-            return;
-        }
-        
-        if (!audContext.isVerified()) {
-            final boolean allow = allowUntrustedAudienceCondition.test(profileRequestContext);
-            if (!allow) {
-                log.warn("{} Client {} not permitted to request access token for untrusted audience {}", getLogPrefix(),
-                        reqContext.getRelyingPartyId(), audContext.getRelyingPartyId());
-                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
-                return;
-            }
-            
-            if (audContext.getProfileConfig() == null) {
-                log.debug("{} Applying previously resolved Token profile configuration to untrusted audience",
-                        getLogPrefix());
-                audContext.setProfileConfig(reqContext.getProfileConfig());
-            }
-        }
-
-        final ProxiedRequesterContext proxyCtx = new ProxiedRequesterContext();
-        proxyCtx.getRequesters().add(audContext.getRelyingPartyId());
-        profileRequestContext.getOutboundMessageContext().addSubcontext(proxyCtx);
-        
-        log.debug("{} Authorized token issuance from {} client ({}) to {} audience ({})", getLogPrefix(),
-                reqContext.isVerified() ? "verified" : "unverified", reqContext.getRelyingPartyId(),
-                        audContext.isVerified() ? "verified" : "unverified", audContext.getRelyingPartyId());
-    }
-
-}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
index c73bb8f5..eaa4a8e0 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateAudience.java
@@ -25,12 +25,16 @@ 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.ProxiedRequesterContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseTokenClaimsContext;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.ClientInfoAudienceLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction;
@@ -42,7 +46,8 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
  * Action that validates requested resource/audience values against a computed set of "allowed"
- * values.
+ * values and populates the resulting set into the {@link OIDCAuthenticationResponseContext}
+ * and a {@link ProxiedRequesterContext}.
  * 
  * <p>This is an ordered list, so the first allowed value determines the "primary" audience of
  * the eventual token.</p>
@@ -51,6 +56,7 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * and be returned, or a failure event will be signaled.</p>
  * 
  * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
  * @event {@link EventIds#ACCESS_DENIED}
  */
 public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
@@ -67,6 +73,9 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
     /** Strategy used to obtain the audience allowed for the client. */
     @Nonnull private Function<ProfileRequestContext,List<String>> allowedAudienceLookupStrategy;
 
+    /** Strategy used for locating/creating the proxy context. */
+    @Nonnull private Function<ProfileRequestContext,ProxiedRequesterContext> proxiedRequesterContextCreationStrategy;
+    
     /** Strategy used to locate the {@link OIDCAuthenticationResponseTokenClaimsContext}. */
     @Nonnull
     private Function<ProfileRequestContext,OIDCAuthenticationResponseTokenClaimsContext>
@@ -78,6 +87,8 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
         relyingPartyIdLookupStrategy = new RelyingPartyIdLookupFunction();
         allowedAudienceLookupStrategy = new ClientInfoAudienceLookupFunction().compose(
                 new DefaultOIDCMetadataContextLookupFunction());
+        proxiedRequesterContextCreationStrategy = new ChildContextLookup<>(ProxiedRequesterContext.class, true).compose(
+                new OutboundMessageContextLookup());
     }
 
     /**
@@ -117,6 +128,19 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
                 "Allowed scope lookyp strategy cannot be null");
     }
     
+    /**
+     * Set the strategy used to locate or create the ProxiedRequesterContext.
+     * 
+     * @param strategy lookup/creation strategy
+     */
+    public void setProxiedRequesterContextCreationStrategy(
+            @Nonnull final Function<ProfileRequestContext,ProxiedRequesterContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        proxiedRequesterContextCreationStrategy = Constraint.isNotNull(strategy,
+                "ProxiedRequesterContext lookup strategy cannot be null");
+    }
+    
 // Checkstyle: CyclomaticComplexity OFF
     /** {@inheritDoc} */
     @Override
@@ -172,10 +196,20 @@ public class ValidateAudience extends AbstractOIDCAuthenticationResponseAction {
         if (effectiveAudience.isEmpty()) {
             log.warn("{} No allowed audience for client {}", getLogPrefix(), clientId);
             ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
-        } else {
-            log.debug("{} Computed audience for client {}: {}", getLogPrefix(), clientId, effectiveAudience);
-            getOidcResponseContext().getAudience().addAll(effectiveAudience);
+            return;
         }
+        
+        log.debug("{} Computed audience for client {}: {}", getLogPrefix(), clientId, effectiveAudience);
+        getOidcResponseContext().getAudience().addAll(effectiveAudience);
+        
+        final ProxiedRequesterContext proxyCtx = proxiedRequesterContextCreationStrategy.apply(profileRequestContext);
+        if (proxyCtx == null) {
+            log.error("{} Unable to locate/create ProxiedRequesterContext", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return;
+        }
+        
+        proxyCtx.getRequesters().addAll(effectiveAudience);
     }
 // Checkstyle: CyclomaticComplexity ON
     
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 0cd017fd..f6a04206 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -280,11 +280,7 @@
     <bean id="SelectAudienceProfileConfiguration"
         class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
         p:relyingPartyContextLookupStrategy-ref="AudienceRelyingPartyCreationStrategy"
-        p:failIfMissing="false" />
-
-    <bean id="EnforceAudienceConfiguration"
-        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.EnforceAudienceConfiguration"
-        scope="prototype" />
+        p:profileId="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration).PROFILE_ID}" />
 
     <bean id="ResolveAttributesForAudience" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
         c:resolverService-ref="shibboleth.AttributeResolverService"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
index b8222360..ee791e85 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-flow.xml
@@ -119,13 +119,6 @@
         <evaluate expression="SelectAudienceProfileConfiguration" />
         <evaluate expression="'proceed'" />
 
-        <transition on="proceed" to="EnforceAudienceConfiguration" />
-    </action-state>
-
-    <action-state id="EnforceAudienceConfiguration">
-        <evaluate expression="EnforceAudienceConfiguration" />
-        <evaluate expression="'proceed'" />
-        
         <transition on="proceed" to="CheckAttributeResolutionForAudience" />
     </action-state>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 960407b3..629616c2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -25,7 +25,6 @@
         p:encryptionOptional="%{idp.oidc.encryptionOptional:true}"
         p:forcePKCE="%{idp.oidc.forcePKCE:false}"
         p:allowPKCEPlain="%{idp.oidc.allowPKCEPlain:false}"
-        p:allowUntrustedAudience="%{idp.oauth2.allowUntrustedAudience:false}"
         p:iDTokenLifetime="%{idp.oidc.idToken.defaultLifetime:PT1H}"
         p:accessTokenLifetime="%{idp.oidc.accessToken.defaultLifetime:PT10M}"
         p:refreshTokenLifetime="%{idp.oidc.refreshToken.defaultLifetime:PT2H}"
@@ -64,6 +63,13 @@
     <bean id="OIDC.Keyset" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.config.OIDCPublishKeySetConfiguration"
         p:securityConfiguration-ref="shibboleth.oidc.PublishKeySetSecurityConfiguration" />
+
+    <bean id="OAUTH2.TokenAudience" parent="AbstractOIDCProfile" lazy-init="true"
+        class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration"
+        p:issuer-ref="issuer"
+        p:encryptionOptional="%{idp.oauth2.encryptionOptional:true}"
+        p:accessTokenType="%{idp.oauth2.accessToken.type:}"
+        p:accessTokenLifetime="%{idp.oauth2.accessToken.defaultLifetime:PT10M}" />
         
     <bean id="OAUTH2.Introspection" parent="AbstractOIDCProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration"
@@ -187,14 +193,6 @@
                 <constructor-arg value="%{idp.oidc.encryptionOptional:true}" />
             </bean>
         </property>
-        <property name="allowUntrustedAudiencePredicate">
-            <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
-                <constructor-arg>
-                    <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="allowUntrustedAudience" />
-                </constructor-arg>
-                <constructor-arg value="%{idp.oauth2.allowUntrustedAudience:false}" />
-            </bean>
-        </property>
         <property name="iDTokenLifetimeLookupStrategy">
             <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="iDTokenLifetime"
                 p:defaultValue="%{idp.oidc.idToken.defaultLifetime:PT1H}" />
@@ -372,9 +370,40 @@
                 p:defaultValue-ref="shibboleth.oidc.PublishKeySetSecurityConfiguration" />
         </property>
     </bean>
-            
-    <bean id="OAUTH2.Revocation.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
-            class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration">
+
+    <bean id="OAUTH2.TokenAudience.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
+            class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenAudienceConfiguration">
+        <property name="issuerLookupStrategy">
+            <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="issuer" p:defaultValue-ref="issuer" />
+        </property>
+        <property name="resolveAttributesPredicate">
+            <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+                <constructor-arg>
+                    <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="resolveAttributes" />
+                </constructor-arg>
+                <constructor-arg value="true" />
+            </bean>
+        </property>
+        <property name="encryptionOptionalPredicate">
+            <bean class="net.shibboleth.utilities.java.support.logic.PredicateSupport" factory-method="fromFunction">
+                <constructor-arg>
+                    <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="encryptionOptional" />
+                </constructor-arg>
+                <constructor-arg value="%{idp.oauth2.encryptionOptional:true}" />
+            </bean>
+        </property>
+        <property name="accessTokenTypeLookupStrategy">
+            <bean parent="shibboleth.MDDrivenStringProperty" p:propertyName="accessTokenType"
+                p:defaultValue="%{idp.oauth2.accessToken.type:}" />
+        </property>
+        <property name="accessTokenLifetimeLookupStrategy">
+            <bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="accessTokenLifetime"
+                p:defaultValue="%{idp.oauth2.accessToken.defaultLifetime:PT10M}" />
+        </property>
+    </bean>
+
+    <bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
+            class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration">
         <property name="tokenEndpointAuthMethodsLookupStrategy">
             <bean parent="shibboleth.MDDrivenSetProperty" p:propertyName="tokenEndpointAuthMethods">
                 <property name="defaultValue">
@@ -388,12 +417,12 @@
         <property name="issuedClaimsValidatorLookupStrategy">
             <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
                 p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
-                p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
+                p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
         </property>
     </bean>
-
-    <bean id="OAUTH2.Introspection.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
-            class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenIntrospectionConfiguration">
+            
+    <bean id="OAUTH2.Revocation.MDDriven" parent="AbstractMDDrivenOAuthTokenValidatingProfile" lazy-init="true"
+            class="net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenRevocationConfiguration">
         <property name="tokenEndpointAuthMethodsLookupStrategy">
             <bean parent="shibboleth.MDDrivenSetProperty" p:propertyName="tokenEndpointAuthMethods">
                 <property name="defaultValue">
@@ -407,7 +436,7 @@
         <property name="issuedClaimsValidatorLookupStrategy">
             <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="issuedClaimsValidator"
                 p:propertyType="#{T(net.shibboleth.oidc.jwt.claims.ClaimsValidator)}"
-                p:defaultValue-ref="DefaultIntrospectionJWTClaimsValidator" />
+                p:defaultValue-ref="DefaultRevocationJWTClaimsValidator" />
         </property>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
index 51f597ec..402f5de4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcFlowTest.java
@@ -200,11 +200,13 @@ public abstract class AbstractOidcFlowTest extends AbstractFlowTest {
         metadata.setGrantTypes(new HashSet<GrantType>(List.of(GrantType.AUTHORIZATION_CODE,
                 GrantType.REFRESH_TOKEN, GrantType.CLIENT_CREDENTIALS)));
         final HashSet<URI> uris = new HashSet<>();
-        for (final String uri : redirectUri) {
-            try {
-                uris.add(new URI(uri));
-            } catch (final URISyntaxException e) {
-                e.printStackTrace();
+        if (redirectUri != null) {
+            for (final String uri : redirectUri) {
+                try {
+                    uris.add(new URI(uri));
+                } catch (final URISyntaxException e) {
+                    throw new IOException(e);
+                }
             }
         }
         final HashSet<ResponseType> responseTypes = new HashSet<>();
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
index 5cb9d1ce..b61b5048 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/ClientCredentialsTokenFlowTest.java
@@ -24,6 +24,7 @@ import static org.testng.Assert.assertTrue;
 
 import java.io.IOException;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.text.ParseException;
 import java.time.Instant;
 import java.util.Collection;
@@ -42,7 +43,13 @@ import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWEDecrypter;
 import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.crypto.RSADecrypter;
+import com.nimbusds.jwt.EncryptedJWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.AccessTokenResponse;
@@ -129,20 +136,6 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
                 Collections.singletonList(resource), "eduPersonScopedAffiliation");
     }
 
-    @Test
-    public void testNoScopeJWT() throws Exception {
-        setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
-        storeMetadata(storageService, clientId + "JWT", clientSecret, null);
-        setBasicAuth(clientId + "JWT", clientSecret);
-        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
-        Assert.assertNotNull(response.getTokens().getBearerAccessToken());
-        Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
-        Assert.assertNull(response.getTokens().getBearerAccessToken().getScope());
-        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), new Scope(),
-                Collections.singletonList(resource), "eduPersonScopedAffiliation");
-    }
-
     @Test
     public void testRequestedScope() throws Exception {
         setHttpFormRequest("POST", createRequestParameters(clientId, scope, resource));
@@ -158,9 +151,10 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     }
 
     @Test
-    public void testRequestedScopeJWTUnverifiedAudience() throws Exception {
+    public void testRequestedScopeJWTVerifiedAudience() throws Exception {
         setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
         storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
+        storeMetadata(storageService, resource, null, null);
         setBasicAuth(clientId + "JWT", clientSecret);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
@@ -172,20 +166,22 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     }
 
     @Test
-    public void testRequestedScopeJWTVerifiedAudience() throws Exception {
+    public void testRequestedScopeJWTVerifiedAudienceEncrypted() throws Exception {
         setHttpFormRequest("POST", createRequestParameters(clientId + "JWT", scope, resource));
         storeMetadata(storageService, clientId + "JWT", clientSecret, scope);
-        storeMetadata(storageService, resource, null, null);
+        storeMetadata(storageService, resource, null, null, JWSAlgorithm.RS256, JWEAlgorithm.RSA_OAEP_256,
+                EncryptionMethod.A128GCM, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, JWSAlgorithm.RS256,
+                rsaPublicKey, (String[]) null);
         setBasicAuth(clientId + "JWT", clientSecret);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final AccessTokenResponse response = parseSuccessResponse(result, AccessTokenResponse.class);
         Assert.assertNotNull(response.getTokens().getBearerAccessToken());
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getLifetime(), 600);
         Assert.assertEquals(response.getTokens().getBearerAccessToken().getScope(), scope);
-        verifyClaims("JWT", response.getTokens().getBearerAccessToken(), scope,
+        verifyClaims("JWE", response.getTokens().getBearerAccessToken(), scope,
                 Collections.singletonList(resource), "email", "eduPersonScopedAffiliation");
     }
-    
+
     @Test
     public void testInvalidSecretJWTAuthn() throws Exception {
         final ClientSecretJWT clientAuth = buildSecretJwtAuth(clientSecret + "invalid");
@@ -276,10 +272,12 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
     * @throws DataSealerException 
     * @throws ParseException 
     * @throws NoSuchAlgorithmException 
+    * @throws JOSEException 
     */
    private void verifyClaims(@Nullable final String type, @Nonnull final AccessToken token, @Nonnull final Scope s,
            @Nonnull @NonnullElements final Collection<String> audiences, @Nullable final String...customClaims)
-           throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException {
+           throws NoSuchAlgorithmException, ParseException, DataSealerException, ComponentInitializationException,
+               JOSEException {
        
        if (type == null) {
            final AccessTokenClaimsSet at = AccessTokenClaimsSet.parse(token.getValue(),
@@ -300,25 +298,39 @@ public class ClientCredentialsTokenFlowTest extends AbstractOidcClientAuthentica
                    assertNull(at.getClaimsSet().getClaim(c));
                }
            }
+           return;
+       }
+
+       final JWTClaimsSet claims;
+       final String clientIdSuffix;
+       
+       if ("JWE".equals(type)) {
+           final EncryptedJWT encrypted = EncryptedJWT.parse(token.getValue());
+           final JWEDecrypter decrypter = new RSADecrypter((PrivateKey) rsaPrivateKey);
+           encrypted.decrypt(decrypter);
+           claims = SignedJWT.parse(encrypted.getPayload().toString()).getJWTClaimsSet();
+           clientIdSuffix = "JWT";
        } else if ("JWT".equals(type)) {
-           final JWTClaimsSet claims = SignedJWT.parse(token.getValue()).getJWTClaimsSet();
-           assertNotNull(claims);
-           assertEquals(claims.getClaim(TokenClaimsSet.KEY_ACR), null);
-           assertEquals(claims.getAudience(), audiences);
-           assertTrue(claims.getDateClaim(TokenClaimsSet.KEY_AUTH_TIME).toInstant().isBefore(Instant.now()));
-           assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_CLIENTID), clientId + type);
-           assertEquals(claims.getExpirationTime().toInstant(), claims.getIssueTime().toInstant().plusSeconds(600));
-           assertEquals(claims.getIssuer(), "https://op.example.org");
-           assertTrue(claims.getIssueTime().toInstant().isBefore(Instant.now()));
-           assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_SCOPE), s.toString());
-           assertEquals(claims.getSubject(), clientId + type);
-           if (customClaims != null) {
-               for (final String c : customClaims) {
-                   assertNotNull(claims.getClaim(c));
-               }
-           }
+           claims = SignedJWT.parse(token.getValue()).getJWTClaimsSet();
+           clientIdSuffix = type;
        } else {
-           throw new RuntimeException("Bad token format");
+           throw new RuntimeException("Bad token type");
+       }
+       
+       assertNotNull(claims);
+       assertEquals(claims.getClaim(TokenClaimsSet.KEY_ACR), null);
+       assertEquals(claims.getAudience(), audiences);
+       assertTrue(claims.getDateClaim(TokenClaimsSet.KEY_AUTH_TIME).toInstant().isBefore(Instant.now()));
+       assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_CLIENTID), clientId + clientIdSuffix);
+       assertEquals(claims.getExpirationTime().toInstant(), claims.getIssueTime().toInstant().plusSeconds(600));
+       assertEquals(claims.getIssuer(), "https://op.example.org");
+       assertTrue(claims.getIssueTime().toInstant().isBefore(Instant.now()));
+       assertEquals(claims.getStringClaim(TokenClaimsSet.KEY_SCOPE), s.toString());
+       assertEquals(claims.getSubject(), clientId + clientIdSuffix);
+       if (customClaims != null) {
+           for (final String c : customClaims) {
+               assertNotNull(claims.getClaim(c));
+           }
        }
    }
    
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
index 9a562883..3739d367 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/relying-party.xml
@@ -23,9 +23,10 @@
     <bean id="shibboleth.UnverifiedRelyingParty" parent="RelyingParty">
         <property name="profileConfigurations">
             <list>
-                <ref bean="OIDC.Keyset.MDDriven" />
-                <ref bean="OIDC.Registration.MDDriven" />
-                <ref bean="OIDC.Configuration.MDDriven" />
+                <ref bean="OIDC.Keyset" />
+                <ref bean="OIDC.Registration" />
+                <ref bean="OIDC.Configuration" />
+                <ref bean="OAUTH2.TokenAudience" /> 
             </list>
         </property>
     </bean>
@@ -44,7 +45,7 @@
                 <ref bean="SAML2.AttributeQuery.MDDriven" />
                 <ref bean="SAML2.ArtifactResolution.MDDriven" />
                 <ref bean="OIDC.SSO.MDDriven" />
-                <bean parent="OIDC.Token.MDDriven" p:allowUntrustedAudience="true" />
+                <ref bean="OIDC.Token.MDDriven" />
                 <ref bean="OIDC.UserInfo.MDDriven" />
                 <ref bean="OIDC.Registration.MDDriven" />
                 <ref bean="OAUTH2.Revocation.MDDriven" />
@@ -81,14 +82,14 @@
         <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdJWT">
             <property name="profileConfigurations">
                  <list>
-                     <bean parent="OIDC.Token.MDDriven" p:accessTokenType="JWT" p:allowUntrustedAudience="true" />
+                     <bean parent="OIDC.Token.MDDriven"/>
                  </list>
             </property>
         </bean>
         <bean parent="RelyingPartyByName" c:relyingPartyIds="https://rp.example.org">
             <property name="profileConfigurations">
                  <list>
-                     <bean parent="OIDC.Token.MDDriven" p:accessTokenType="JWT" />
+                     <bean parent="OAUTH2.TokenAudience.MDDriven" p:accessTokenType="JWT" />
                  </list>
             </property>
         </bean>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list