[java-idp-oidc] branch main updated: JOIDC-111 - Support manipulating claims encoded inside authz code and tokens
Henri Mikkonen
henri.mikkonen at iki.fi
Fri May 27 12:41:39 UTC 2022
This is an automated email from the git hooks/post-receive script.
hjmikkon 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=768db0d69ea9201e7cb985ed95cde1332ec26842
The following commit(s) were added to refs/heads/main by this push:
new 768db0d6 JOIDC-111 - Support manipulating claims encoded inside authz code and tokens
768db0d6 is described below
commit 768db0d69ea9201e7cb985ed95cde1332ec26842
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 27 15:40:01 2022 +0300
JOIDC-111 - Support manipulating claims encoded inside authz code and tokens
https://shibboleth.atlassian.net/browse/JOIDC-111
Wired the new profile configuration parameters regarding token set manipulation.
New function 'shibboleth.oidc.TokenRequestTokenClaimsSetLookupFunction' helps in
fetching custom values added to the tokens / authz codes.
---
.../TokenRequestTokenClaimsSetLookupFunction.java | 52 ++++++++++++++++++++
.../op/oauth2/profile/impl/BuildAccessToken.java | 52 +++++++++++++++++++-
.../SetAuthorizationCodeToResponseContext.java | 56 +++++++++++++++++++++-
.../impl/SetRefreshTokenToResponseContext.java | 56 +++++++++++++++++++++-
.../META-INF/net.shibboleth.idp/postconfig.xml | 4 ++
.../idp/service/relying-party/postconfig.xml | 12 +++++
.../oidc/op/profile/flow/AuthorizeFlowTest.java | 56 +++++++++++++++++++++-
.../plugin/oidc/op/profile/flow/TokenFlowTest.java | 32 +++++++++++++
.../src/test/resources/conf/global.xml | 38 ++++++++++++++-
.../src/test/resources/conf/relying-party.xml | 8 ++++
10 files changed, 361 insertions(+), 5 deletions(-)
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestTokenClaimsSetLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestTokenClaimsSetLookupFunction.java
new file mode 100644
index 00000000..9ac65825
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/TokenRequestTokenClaimsSetLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.profile.context.navigate;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+
+/**
+ * A bi-function that returns value for the given claim via a lookup function. This lookup locates the claim from token
+ * claims set. If token claims set nor the desired claim in the set are not available, null is returned.
+ */
+public class TokenRequestTokenClaimsSetLookupFunction implements BiFunction<ProfileRequestContext, String, Object> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Object apply(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final String claim) {
+ final Function<ProfileRequestContext, Object> claimsSetLookup =
+ new AbstractTokenClaimsLookupFunction<Object>() {
+
+ @Override
+ Object doLookup(final @Nonnull TokenClaimsSet tokenClaims) {
+ return tokenClaims.getClaimsSet().getClaim(claim);
+ }
+
+ };
+ return claimsSetLookup.apply(profileRequestContext);
+ }
+
+}
\ 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/BuildAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
index 55a783ed..e36a273b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/BuildAccessToken.java
@@ -22,6 +22,7 @@ import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -59,6 +60,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet.Buil
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
import net.shibboleth.oidc.profile.config.logic.AttributeConsentFlowEnabledPredicate;
+import net.shibboleth.oidc.profile.config.navigate.AccessTokenClaimsSetManipulationStrategyLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.AccessTokenLifetimeLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.AccessTokenTypeLookupFunction;
@@ -133,6 +135,14 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
/** Strategy used to create the subcontext to hold the token. */
@Nonnull private Function<ProfileRequestContext,AccessTokenContext> accessTokenContextCreationStrategy;
+ /** Lookup function to supply strategy bi-function for manipulating token claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ tokenClaimsSetManipulationStrategyLookupStrategy;
+
+ /** The strategy used for manipulating the token claims set */
+ @Nullable private BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> manipulationStrategy;
+
/** Authorize Code / Refresh Token the access token is based on, if any. */
@Nullable private TokenClaimsSet tokenClaimsSet;
@@ -175,6 +185,8 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
accessTokenContextCreationStrategy = new ChildContextLookup<>(AccessTokenContext.class, true).compose(
new ChildContextLookup<>(OIDCAuthenticationResponseContext.class).compose(
new OutboundMessageContextLookup()));
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ new AccessTokenClaimsSetManipulationStrategyLookupFunction();
}
/**
@@ -298,6 +310,20 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
Constraint.isNotNull(strategy, "AccessTokenContext creation strategy cannot be null");
}
+ /**
+ * Set the lookup function to supply strategy bi-function for manipulating token claims set.
+ *
+ * @param strategy What to set
+ */
+ public void setTokenClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ Constraint.isNotNull(strategy, "Manipulation strategy lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -374,6 +400,9 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
return false;
}
accessTokenCtx.setLifetime(lifetime);
+
+ manipulationStrategy = tokenClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+
return true;
}
@@ -460,7 +489,28 @@ public class BuildAccessToken extends AbstractOIDCResponseAction {
}
final AccessTokenClaimsSet claimsSet = builder.build();
-
+
+ if (manipulationStrategy != null) {
+ log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
+ claimsSet.serialize());
+ final Map<String, Object> result = manipulationStrategy.apply(profileRequestContext,
+ claimsSet.getClaimsSet().toJSONObject());
+ if (result == null) {
+ log.debug("{} Manipulation strategy returned null, leaving token claims set untouched.", getLogPrefix());
+ } else {
+ log.debug("{} Applying the manipulated claims into the token claims set", getLogPrefix());
+ try {
+ claimsSet.setClaimsSet(JWTClaimsSet.parse(result));
+ } catch (final ParseException e) {
+ log.error("{} The resulted claims set could not be transformed into ", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+ }
+ } else {
+ log.debug("{} No manipulation strategy configured", getLogPrefix());
+ }
+
try {
if (jwtTokenType) {
accessTokenCtx.setJWT(new PlainJWT(sealClaims(claimsSet.getClaimsSet())));
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
index 9b784a4a..e8142371 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetAuthorizationCodeToResponseContext.java
@@ -17,8 +17,11 @@
package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
+import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -29,6 +32,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.minidev.json.JSONArray;
@@ -44,6 +48,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
import net.shibboleth.idp.profile.IdPEventIds;
import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
import net.shibboleth.oidc.profile.config.logic.AttributeConsentFlowEnabledPredicate;
+import net.shibboleth.oidc.profile.config.navigate.AuthorizationCodeClaimsSetManipulationStrategyLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.AuthzCodeLifetimeLookupFunction;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
@@ -102,6 +107,14 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
/** Strategy used to locate the code challenge method. */
@Nonnull private Function<ProfileRequestContext, String> codeChallengeMethodLookupStrategy;
+ /** Lookup function to supply strategy bi-function for manipulating token claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ tokenClaimsSetManipulationStrategyLookupStrategy;
+
+ /** The strategy used for manipulating the token claims set. */
+ @Nullable private BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> manipulationStrategy;
+
/** Subject context. */
@Nullable private SubjectContext subjectCtx;
@@ -127,6 +140,8 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
issuerLookupStrategy = new ResponderIdLookupFunction();
consentEnabledPredicate = new AttributeConsentFlowEnabledPredicate();
idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ new AuthorizationCodeClaimsSetManipulationStrategyLookupFunction();
}
/**
@@ -234,6 +249,20 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
Constraint.isNotNull(predicate, "predicate used to check if consent is enabled cannot be null");
}
+ /**
+ * Set the lookup function to supply strategy bi-function for manipulating token claims set.
+ *
+ * @param strategy What to set
+ */
+ public void setTokenClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ Constraint.isNotNull(strategy, "Manipulation strategy lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -281,7 +310,9 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
// Default method is "plain"
codeChallenge = (codeChallengeMethod != null ? codeChallengeMethod : "plain") + codeChallenge;
}
-
+
+ manipulationStrategy = tokenClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+
return true;
}
@@ -328,6 +359,29 @@ public class SetAuthorizationCodeToResponseContext extends AbstractOIDCAuthentic
.setConsentedClaims(consented)
.setConsentEnabled(consentEnabledPredicate.test(profileRequestContext))
.build();
+
+ if (manipulationStrategy != null) {
+ log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
+ claimsSet.serialize());
+ final Map<String, Object> result = manipulationStrategy.apply(profileRequestContext,
+ claimsSet.getClaimsSet().toJSONObject());
+ if (result == null) {
+ log.debug("{} Manipulation strategy returned null, leaving token claims set untouched.",
+ getLogPrefix());
+ } else {
+ log.debug("{} Applying the manipulated claims into the token claims set", getLogPrefix());
+ try {
+ claimsSet.setClaimsSet(JWTClaimsSet.parse(result));
+ } catch (final ParseException e) {
+ log.error("{} The resulted claims set could not be transformed into ", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+ }
+ } else {
+ log.debug("{} No manipulation strategy configured", getLogPrefix());
+ }
+
// We set token claims set to response context for possible access token generation.
responseCtx.setAuthorizationGrantClaimsSet(claimsSet);
try {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
index 81afb7d4..efa73e06 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/SetRefreshTokenToResponseContext.java
@@ -17,8 +17,11 @@
package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
+import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -29,6 +32,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.openid.connect.sdk.OIDCScopeValue;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
@@ -36,6 +40,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.config.navigate.RefreshTokenClaimsSetManipulationStrategyLookupFunction;
import net.shibboleth.oidc.profile.config.navigate.RefreshTokenLifetimeLookupFunction;
import org.opensaml.profile.action.ActionSupport;
@@ -62,6 +67,14 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
/** Strategy used to obtain the refresh token lifetime. */
@Nonnull private Function<ProfileRequestContext,Duration> refreshTokenLifetimeLookupStrategy;
+ /** Lookup function to supply strategy bi-function for manipulating token claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ tokenClaimsSetManipulationStrategyLookupStrategy;
+
+ /** The strategy used for manipulating the token claims set. */
+ @Nullable private BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> manipulationStrategy;
+
/** Authorize Code / Refresh Token the refresh token will be based on. */
@Nullable private TokenClaimsSet tokenClaimsSet;
@@ -76,6 +89,8 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
public SetRefreshTokenToResponseContext(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
refreshTokenLifetimeLookupStrategy = new RefreshTokenLifetimeLookupFunction();
dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ new RefreshTokenClaimsSetManipulationStrategyLookupFunction();
}
/**
@@ -91,6 +106,20 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
Constraint.isNotNull(strategy, "Refresh token lifetime lookup strategy cannot be null");
}
+ /**
+ * Set the lookup function to supply strategy bi-function for manipulating token claims set.
+ *
+ * @param strategy What to set
+ */
+ public void setTokenClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ tokenClaimsSetManipulationStrategyLookupStrategy =
+ Constraint.isNotNull(strategy, "Manipulation strategy lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -118,7 +147,9 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
return false;
}
-
+
+ manipulationStrategy = tokenClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+
return true;
}
@@ -128,6 +159,29 @@ public class SetRefreshTokenToResponseContext extends AbstractOIDCResponseAction
final Instant dateExp = Instant.now().plus(refreshTokenLifetime);
final RefreshTokenClaimsSet claimsSet =
new RefreshTokenClaimsSet.Builder(tokenClaimsSet, Instant.now(), dateExp).build();
+
+ if (manipulationStrategy != null) {
+ log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
+ claimsSet.serialize());
+ final Map<String, Object> result = manipulationStrategy.apply(profileRequestContext,
+ claimsSet.getClaimsSet().toJSONObject());
+ if (result == null) {
+ log.debug("{} Manipulation strategy returned null, leaving token claims set untouched.",
+ getLogPrefix());
+ } else {
+ log.debug("{} Applying the manipulated claims into the token claims set", getLogPrefix());
+ try {
+ claimsSet.setClaimsSet(JWTClaimsSet.parse(result));
+ } catch (final ParseException e) {
+ log.error("{} The resulted claims set could not be transformed into ", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+ }
+ } else {
+ log.debug("{} No manipulation strategy configured", getLogPrefix());
+ }
+
try {
getOidcResponseContext().setRefreshToken(claimsSet.serialize(dataSealer));
log.debug("{} Setting refresh token {} as {} to response context ", getLogPrefix(), claimsSet.serialize(),
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index ce0505dc..acbfef4a 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -543,4 +543,8 @@
</property>
</bean>
+ <bean id="shibboleth.oidc.TokenRequestTokenClaimsSetLookupFunction"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.TokenRequestTokenClaimsSetLookupFunction">
+ </bean>
+
</beans>
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 f84d4742..99f7170d 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
@@ -240,6 +240,10 @@
<bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="IDTokenManipulationStrategy"
p:propertyType="#{T(java.util.function.Function)}" />
</property>
+ <property name="accessTokenClaimsSetManipulationStrategyLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="accessTokenClaimsSetManipulationStrategy"
+ p:propertyType="#{T(java.util.function.Function)}" />
+ </property>
</bean>
<bean id="OIDC.SSO.MDDriven" parent="AbstractMDDrivenOIDCSSOProfile" lazy-init="true"
@@ -295,6 +299,10 @@
<constructor-arg value="false" />
</bean>
</property>
+ <property name="authorizationCodeClaimsSetManipulationStrategyLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="authorizationCodeClaimsSetManipulationStrategy"
+ p:propertyType="#{T(java.util.function.Function)}" />
+ </property>
</bean>
<bean id="OIDC.UserInfo.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
@@ -396,6 +404,10 @@
<bean parent="shibboleth.MDDrivenDurationProperty" p:propertyName="refreshTokenLifetime"
p:defaultValue="%{idp.oidc.refreshToken.defaultLifetime:PT2H}" />
</property>
+ <property name="refreshTokenClaimsSetManipulationStrategyLookupStrategy">
+ <bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="refreshTokenClaimsSetManipulationStrategy"
+ p:propertyType="#{T(java.util.function.Function)}" />
+ </property>
</bean>
<bean id="OAUTH2.TokenAudience.MDDriven" parent="AbstractMDDrivenOIDCProfile" lazy-init="true"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 89abe9c7..575adcfd 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -39,7 +39,6 @@ import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.PlainJWT;
import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.ErrorResponse;
import com.nimbusds.oauth2.sdk.Response;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
@@ -66,6 +65,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
String redirectUri = "https://example.org/cb";
String clientId = "mockClientId";
String clientIdIssInResponse = "mockClientIdIssInResponse";
+ String clientIdCustomTokens = "mockClientIdCustomTokens";
String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
Scope scope = Scope.parse("openid profile email");
@@ -466,6 +466,33 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
Assert.assertNull(token.getClaimsSet().getStringClaim("eduPersonScopedAffiliation"));
}
+ @Test
+ public void testWithHybridIdTokenTokenFlowWithCustomTokenClaim() throws IOException, SessionException, ParseException, DataSealerException {
+ request.setMethod("GET");
+ request.setQueryString("client_id=mockClientIdCustomTokens&response_type=code+id_token+token&scope=openid%20profile"
+ + "&redirect_uri="+ redirectUri + "&nonce=idhas3h23hi13h1o2i32");
+ storeMetadata(storageService, clientIdCustomTokens, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNotNull(successResponse.getIDToken());
+ Assert.assertNotNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNull(successResponse.getIssuer());
+
+ final AccessTokenClaimsSet token =
+ AccessTokenClaimsSet.parse(successResponse.getAccessToken().getValue(), getDataSealer());
+ Assert.assertEquals(token.getAudience(), Collections.singletonList(issuer));
+ Assert.assertNull(token.getClaimsSet().getStringClaim("eduPersonScopedAffiliation"));
+ final String customClaim = token.getClaimsSet().getStringClaim("custom_access_token_claim");
+ Assert.assertNotNull(customClaim);
+ Assert.assertEquals(customClaim, "value2");
+ }
+
@Test
public void testWithHybridIdTokenTokenFlowAndResource() throws IOException, SessionException, ParseException {
request.setMethod("GET");
@@ -706,6 +733,32 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
Assert.assertEquals(email.getClaimRequirement(), ClaimRequirement.ESSENTIAL);
}
+ @Test
+ public void testWithAuthorizationCodeFlowWithCustomClaimInCode() throws IOException, SessionException, DataSealerException, ParseException {
+ request.setMethod("GET");
+ request.setQueryString("client_id=mockClientIdCustomTokens&response_type=code&scope=openid%20profile"
+ + "&claims=%7B%22userinfo%22%3A%7B%22email%22%3A%7B%22essential%22%3Atrue%7D%7D%7D"
+ + "&redirect_uri=" + redirectUri);
+ storeMetadata(storageService, clientIdCustomTokens, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNull(successResponse.getIssuer());
+
+ final AuthorizeCodeClaimsSet code =
+ AuthorizeCodeClaimsSet.parse(successResponse.getAuthorizationCode().getValue(), getDataSealer());
+ final Object customClaim = code.getClaimsSet().getClaim("custom_code_claim");
+ Assert.assertNotNull(customClaim);
+ Assert.assertEquals(customClaim, "value1");
+ }
+
@Test
public void testWithAuthorizationCodeFlowUsingSAMLMetadata() throws IOException, SessionException {
request.setMethod("GET");
@@ -1007,6 +1060,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
removeMetadata(storageService, "mockClientIdPKCEPlainUnforced");
removeMetadata(storageService, "mockClientIdPKCEPlain");
removeMetadata(storageService, "mockClientIdPKCES256");
+ removeMetadata(storageService, "mockClientIdCustomTokens");
removeMetadata(storageService, clientIdIssInResponse);
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index b1d6b8b0..c2b4edd1 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -52,6 +52,7 @@ import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
import net.minidev.json.JSONObject;
import net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateGrantTest;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.RefreshTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
import net.shibboleth.utilities.java.support.collection.Pair;
import net.shibboleth.utilities.java.support.security.DataSealerException;
@@ -70,6 +71,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
String clientIdPkcePlainPublic = "mockPublicClientIdPKCEPlain";
String clientIdPkcePlainUnforcedPublic = "mockPublicClientIdPKCEPlainUnforced";
String clientIdPkceS256Public = "mockPublicClientIdPKCES256";
+ String clientIdCustomTokens = "mockClientIdCustomTokens";
String codeVerifier = "9234567812345678123456781234567812345678123456781234567812345678";
Scope scope = Scope.parse("openid profile email offline_access");
@@ -87,6 +89,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
removeMetadata(storageService, clientId);
removeMetadata(storageService, clientIdPkcePlain);
removeMetadata(storageService, clientIdPkceS256);
+ removeMetadata(storageService, clientIdCustomTokens);
}
@Test
@@ -178,6 +181,35 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
Assert.assertNotNull(response.getOIDCTokens().getIDToken());
}
+ @Test
+ public void testValidGrantWithCustomTokens() throws Exception {
+ initializeGrantAndRequest(clientIdCustomTokens, createRequestParameters(redirectUri, "authorization_code",
+ buildAuthorizationCode(clientIdCustomTokens), clientId));
+ storeConsent(storageService, "jdoe", clientIdCustomTokens, "mail");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ Assert.assertNotNull(response.getTokens().getRefreshToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+
+ final AccessTokenClaimsSet accessToken =
+ AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
+ final String atCustomAtClaim = accessToken.getClaimsSet().getStringClaim("custom_access_token_claim");
+ final String atCustomRtClaim = accessToken.getClaimsSet().getStringClaim("custom_refresh_token_claim");
+ Assert.assertNotNull(atCustomAtClaim);
+ Assert.assertEquals(atCustomAtClaim, "value2");
+ Assert.assertNull(atCustomRtClaim);
+
+ final RefreshTokenClaimsSet refreshToken =
+ RefreshTokenClaimsSet.parse(response.getTokens().getRefreshToken().getValue(), getDataSealer());
+ final String rtCustomAtClaim = refreshToken.getClaimsSet().getStringClaim("custom_access_token_claim");
+ final String rtCustomRtClaim = refreshToken.getClaimsSet().getStringClaim("custom_refresh_token_claim");
+ Assert.assertNotNull(rtCustomRtClaim);
+ Assert.assertEquals(rtCustomRtClaim, "value3");
+ Assert.assertNull(rtCustomAtClaim);
+
+ }
+
@Test
public void testValidGrantWithWrongRegisteredAuthType() throws Exception {
initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "authorization_code",
diff --git a/idp-oidc-extension-impl/src/test/resources/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
index 80c4d163..74c52462 100644
--- a/idp-oidc-extension-impl/src/test/resources/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/conf/global.xml
@@ -38,5 +38,41 @@
<!-- Copy of IdP signing key for tests. -->
<bean id="testbed.DefaultRSSigningCredential" parent="shibboleth.JWKCredential"
p:resource="%{idp.signing.oidc.rs.key}" />
-
+
+ <bean id="testAuthorizationCodeManipulation" parent="shibboleth.BiFunctions.Scripted" factory-method="inlineScript">
+ <constructor-arg name="scriptSource">
+ <value>
+ <![CDATA[
+ newMap = input2;
+ newMap.put("custom_code_claim", "value1");
+ newMap;
+ ]]>
+ </value>
+ </constructor-arg>
+ </bean>
+
+ <bean id="testAccessTokenManipulation" parent="shibboleth.BiFunctions.Scripted" p:customObject-ref="shibboleth.oidc.TokenRequestTokenClaimsSetLookupFunction" factory-method="inlineScript">
+ <constructor-arg name="scriptSource">
+ <value>
+ <![CDATA[
+ newMap = input2;
+ newMap.put("custom_access_token_claim", "value2");
+ newMap;
+ ]]>
+ </value>
+ </constructor-arg>
+ </bean>
+
+ <bean id="testRefreshTokenManipulation" parent="shibboleth.BiFunctions.Scripted" p:customObject-ref="shibboleth.oidc.TokenRequestTokenClaimsSetLookupFunction" factory-method="inlineScript">
+ <constructor-arg name="scriptSource">
+ <value>
+ <![CDATA[
+ newMap = input2;
+ newMap.put("custom_refresh_token_claim", "value3");
+ newMap;
+ ]]>
+ </value>
+ </constructor-arg>
+ </bean>
+
</beans>
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 5f040a41..e6da1687 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
@@ -58,6 +58,14 @@
</bean>
<util:list id="shibboleth.RelyingPartyOverrides">
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdCustomTokens">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO.MDDriven" p:authorizationCodeClaimsSetManipulationStrategy-ref="testAuthorizationCodeManipulation" p:accessTokenClaimsSetManipulationStrategy-ref="testAccessTokenManipulation" />
+ <bean parent="OAUTH2.Token.MDDriven" p:forcePKCE="false" p:allowPKCEPlain="true" p:accessTokenClaimsSetManipulationStrategy-ref="testAccessTokenManipulation" p:refreshTokenClaimsSetManipulationStrategy-ref="testRefreshTokenManipulation"/>
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdIssInResponse">
<property name="profileConfigurations">
<list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list