[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