[java-idp-oidc] branch main updated: JOIDC-104 - Support to manipulate claims within the ID_Token
Henri Mikkonen
henri.mikkonen at iki.fi
Fri May 20 08:00:06 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=7868adb220fdcb924025c3d5733b780ca4d7a2ac
The following commit(s) were added to refs/heads/main by this push:
new 7868adb2 JOIDC-104 - Support to manipulate claims within the ID_Token
7868adb2 is described below
commit 7868adb220fdcb924025c3d5733b780ca4d7a2ac
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 20 10:57:44 2022 +0300
JOIDC-104 - Support to manipulate claims within the ID_Token
https://shibboleth.atlassian.net/browse/JOIDC-104
Initial proposal for the feature, applied to authorize and token
flows. If the BiFunction returns null, the existing id_token claims
set is left as it was.
Initial unit testing exists, but it still needs to be improved.
---
.../profile/impl/ManipulateClaimsForIDToken.java | 132 +++++++++++++++++++++
.../idp/flows/oidc/authorize/authorize-beans.xml | 3 +
.../idp/flows/oidc/authorize/authorize-flow.xml | 1 +
.../idp/flows/oidc/token/token-beans.xml | 3 +
.../shibboleth/idp/flows/oidc/token/token-flow.xml | 1 +
.../impl/ManipulateClaimsForIDTokenTest.java | 103 ++++++++++++++++
6 files changed, 243 insertions(+)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDToken.java
new file mode 100644
index 00000000..7557ec21
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDToken.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements. See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.impl;
+
+import java.text.ParseException;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.config.navigate.IDTokenManipulationStrategyLookupFunction;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * An action that can be used for manipulating id_token claims via configurable strategy (bi-function). If the
+ * function exists, its result (a Map) is used as-is for building new contents for the upcoming id_token, i.e.
+ * the previously built claims set is replaced. If the function returns null, the existing id_token contents
+ * are not replaced.
+ *
+ * @event {@link EventIds#INVALID_MSG_CTX} If the id_token does not exist.
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG} If the new claims set is not compatible with the id_token
+ * requirements (e.g. due to missing mandatory claims, or claims having unexpected syntax).
+ */
+public class ManipulateClaimsForIDToken extends AbstractOIDCAuthenticationResponseAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ManipulateClaimsForIDToken.class);
+
+ /** Lookup function to supply strategy bi-function for manipulating id_token claims. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ idTokenManipulationStrategyLookupStrategy;
+
+ /** The strategy used for manipulating the id_token. */
+ private BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> manipulationStrategy;
+
+ /** The id_token to operate on. */
+ private IDTokenClaimsSet idToken;
+
+ /**
+ * Constructor.
+ */
+ public ManipulateClaimsForIDToken() {
+ idTokenManipulationStrategyLookupStrategy = new IDTokenManipulationStrategyLookupFunction();
+ }
+
+ /**
+ * Set the lookup function to supply strategy bi-function for manipulating id_token claims.
+ *
+ * @param strategy What to set
+ */
+ public void setIDTokenManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+ idTokenManipulationStrategyLookupStrategy =
+ Constraint.isNotNull(strategy, "IDToken manipulation strategy lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ idToken = getOidcResponseContext().getIDToken();
+ if (idToken == null) {
+ log.error("{} No id token", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ manipulationStrategy = idTokenManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ if (manipulationStrategy == null) {
+ log.debug("{} No manipulation strategy resolved, nothing to do.", getLogPrefix());
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final Map<String, Object> result = manipulationStrategy.apply(profileRequestContext, idToken.toJSONObject());
+ if (result == null) {
+ log.debug("{} Manipulation strategy retruned null, leaving id_token claims untouched.", getLogPrefix());
+ return;
+ }
+ log.debug("{} Applying the manipulated claims into the id_token", getLogPrefix());
+ final IDTokenClaimsSet newIdToken;
+ try {
+ newIdToken = new IDTokenClaimsSet(JWTClaimsSet.parse(result));
+ } catch (final ParseException | com.nimbusds.oauth2.sdk.ParseException e) {
+ log.error("{} The resulted claims set could not be transformed into id_token", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+ log.debug("{} Replacing the id_token with the manipulated contents", getLogPrefix());
+ getOidcResponseContext().setIDToken(newIdToken);
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 61033f4a..787b1ad2 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -533,6 +533,9 @@
</property>
</bean>
+ <bean id="ManipulateClaimsForIDToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ManipulateClaimsForIDToken" scope="prototype" />
+
<bean id="SignIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 48096df1..7de875fc 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -255,6 +255,7 @@
<evaluate expression="AddNonceToIDToken" />
<evaluate expression="AddAccessTokenHashToIDToken" />
<evaluate expression="AddAuthorizationCodeHashToIDToken" />
+ <evaluate expression="ManipulateClaimsForIDToken" />
<evaluate expression="SignIDToken" />
<evaluate expression="EncryptIDToken" />
<evaluate expression="'proceed'" />
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 0bd292ec..2a3c1f32 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
@@ -390,6 +390,9 @@
</property>
</bean>
+ <bean id="ManipulateClaimsForIDToken"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ManipulateClaimsForIDToken" scope="prototype" />
+
<bean id="SignIDToken" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.SignIDToken" scope="prototype">
<property name="securityParametersLookupStrategy">
<bean parent="shibboleth.Functions.Compose"
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 1cc2e0bb..c14e9c4b 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
@@ -224,6 +224,7 @@
<evaluate expression="AddAcrToIDToken" />
<evaluate expression="AddNonceToIDToken" />
<evaluate expression="AddAccessTokenHashToIDToken" />
+ <evaluate expression="ManipulateClaimsForIDToken" />
<evaluate expression="SignIDToken" />
<evaluate expression="EncryptIDToken" />
<evaluate expression="'proceed'" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDTokenTest.java
new file mode 100644
index 00000000..0ef45578
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ManipulateClaimsForIDTokenTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.impl;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.mockito.Mockito;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+
+/** {@link ManipulateClaimsForIDToken} unit test. */
+public class ManipulateClaimsForIDTokenTest extends BaseOIDCResponseActionTest {
+
+ private ManipulateClaimsForIDToken action;
+
+ private void init() throws ComponentInitializationException {
+ init(null);
+ }
+
+ private void init(final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy)
+ throws ComponentInitializationException {
+ action = new ManipulateClaimsForIDToken();
+ action.initialize();
+ final OIDCAuthorizationConfiguration config = new OIDCAuthorizationConfiguration();
+ config.setIDTokenManipulationStrategy(strategy);
+ rpCtx.setProfileConfig(config);
+ }
+
+ @Test
+ public void testNoCtx() throws ComponentInitializationException {
+ init();
+ final Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertEvent(event, EventIds.INVALID_MSG_CTX);
+
+ }
+
+ @Test
+ public void testSuccessManipulationForIss() throws ComponentInitializationException, ParseException {
+ final Instant now = Instant.now();
+ setIdTokenToResponseContext("iss", "sub", "aud", now, now);
+ final Map<String, Object> manipulatedClaims = Map.of("iss", "manipulatedIss", "sub", "sub", "aud",
+ List.of("aud"), "exp", now.getEpochSecond(), "iat", now.getEpochSecond());
+ init(mockFunction(manipulatedClaims));
+ final Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertProceedEvent(event);
+ final IDTokenClaimsSet idToken = respCtx.getIDToken();
+ Assert.assertEquals(idToken.getSubject().getValue(), "sub");
+ Assert.assertEquals(idToken.getIssuer().getValue(), "manipulatedIss");
+ Assert.assertEquals(idToken.getAudience(), Audience.create("aud"));
+ Assert.assertEquals(idToken.getIssueTime().toInstant().getEpochSecond(), now.getEpochSecond());
+ Assert.assertEquals(idToken.getExpirationTime().toInstant().getEpochSecond(), now.getEpochSecond());
+ }
+
+ @Test
+ public void testFailedManipulationDueMissingIss() throws ComponentInitializationException, ParseException {
+ final Instant now = Instant.now();
+ setIdTokenToResponseContext("iss", "sub", "aud", now, now);
+ final Map<String, Object> manipulatedClaims = Map.of("sub", "sub", "aud",
+ List.of("aud"), "exp", now.getEpochSecond(), "iat", now.getEpochSecond());
+ init(mockFunction(manipulatedClaims));
+ final Event event = action.execute(requestCtx);
+ ActionTestingSupport.assertEvent(event, IdPEventIds.INVALID_PROFILE_CONFIG);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ protected BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> mockFunction(
+ final Map<String, Object> result) {
+ final BiFunction function = Mockito.mock(BiFunction.class);
+ Mockito.when(function.apply(Mockito.any(), Mockito.any())).thenReturn(result);
+ return function;
+ }
+
+}
\ 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