[java-idp-plugin-vci] 02/02: Moving token flow logic incrementally closet to OP Token flow by unwrapping grant in post decode phase

Codeberg noreply at shibboleth.net
Wed Dec 3 15:16:08 UTC 2025


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-idp-plugin-vci.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-vci/commit/4b748aad80fb875465abb131ccde8fcb55d8d755

commit 4b748aad80fb875465abb131ccde8fcb55d8d755
Author: jlauros <janne.lauros at csc.fi>
AuthorDate: Wed Dec 3 17:15:33 2025 +0200

    Moving token flow logic incrementally closet to OP Token flow by unwrapping grant in post decode phase
---
 .../plugin/openidvci/profile/impl/UnwrapGrant.java | 257 +++++++++++++++++++++
 .../profile/impl/ValidateAuthorizedCodeGrant.java  |  91 --------
 .../impl/ValidatePreAuthorizedCodeGrant.java       | 161 -------------
 .../idp/flows/openid/vci/token/token-beans.xml     |  12 +-
 .../idp/flows/openid/vci/token/token-flow.xml      |  27 ++-
 5 files changed, 281 insertions(+), 267 deletions(-)

diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java
new file mode 100644
index 0000000..37a9358
--- /dev/null
+++ b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/UnwrapGrant.java
@@ -0,0 +1,257 @@
+/*
+ * Licensed 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 org.geant.shibboleth.plugin.openidvci.profile.impl;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
+import org.geant.shibboleth.plugin.openidvci.messaging.impl.AbstractOpenIDVCITokenResponseAction;
+import org.geant.shibboleth.plugin.openidvci.profile.context.navigate.APIRequestClientIDLookupFunction;
+import org.geant.shibboleth.plugin.openidvci.storage.CredentialOfferCache;
+import org.geant.shibboleth.plugin.openidvci.storage.CredentialOfferObject;
+import org.geant.shibboleth.plugin.openidvci.token.support.CredentialOfferClaimsSet;
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
+import com.nimbusds.oauth2.sdk.AuthorizationGrant;
+import com.nimbusds.oauth2.sdk.GrantType;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.shared.security.DataSealerException;
+
+/**
+ * Action that unwraps an authorization grant or pre-authorized grant.
+ * 
+ * <p>
+ * Operation is valid if it is successfully unwrapped, parsed as a code or
+ * refresh token, is unexpired and was issued to the expected client.
+ * 
+ * Pre-authorized grant may also be located from storage.
+ * </p>
+ * 
+ * <p>
+ * The claims set from the authorization grant grant is stored to response context via
+ * {@link OIDCAuthenticationResponseContext#setAuthorizationGrantClaimsSet(TokenClaimsSet)}.
+ * </p>
+ * 
+ * <p>
+ * The credential offer object from pre-authorised grant is stored to response context via
+ * {@link TokenContext#setCredentialOfferObject}.
+ * </p>
+ * 
+ * <p>
+ * The potential credentials from pre-authorised grant is stored to response context via
+ * {@link TokenContext#setPotentialCredentials}.
+ * </p>
+ * 
+ * @since 4.4.0
+ */
+public class UnwrapGrant extends AbstractOpenIDVCITokenResponseAction {
+
+    /** Class logger. */
+    @Nonnull
+    private Logger log = LoggerFactory.getLogger(UnwrapGrant.class);
+
+    /** Data sealer for unwrapping authorization code. */
+    @Nonnull
+    private final DataSealer dataSealer;
+
+    /** Strategy used to obtain the client id value from token request. */
+    @Nonnull
+    private Function<MessageContext, ClientID> clientIDLookupStrategy;
+    
+    @NonnullAfterInit
+    private CredentialOfferCache credentialOfferCache;
+
+    /**
+     * Constructor.
+     * 
+     * @param sealer sealer to decrypt/hmac authorize code.
+     */
+    public UnwrapGrant(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
+        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
+        clientIDLookupStrategy = new APIRequestClientIDLookupFunction();
+    }
+    
+    /**
+     * Set the credential offer cache instance to use.
+     * 
+     * @param cache The credential offer cache to set.
+     */
+    public void setCredentialOfferCache(@Nonnull final CredentialOfferCache cache) {
+        checkSetterPreconditions();
+        credentialOfferCache = Constraint.isNotNull(cache, "CredentialOfferCache cannot be null");
+    }
+
+ 
+    /**
+     * Set the strategy used to locate the client id of the request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        clientIDLookupStrategy = Constraint.isNotNull(strategy,
+                "ClientIDLookupStrategy lookup strategy cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (credentialOfferCache == null) {
+            throw new ComponentInitializationException("CredentialOfferCache cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        final AuthorizationGrant grant = getTokenRequest() != null ? getTokenRequest().getAuthorizationGrant():null;
+        //DO NOT ADD IT YET
+        TokenContext tokenContext = profileRequestContext.getInboundMessageContext().ensureSubcontext(TokenContext.class);
+        if (grant != null) {
+            // Grant type is something OP traditionally understands, not OP
+            // Most of the code executed in this block is from OP Unwrap implementation.
+            log.debug("{} Unwrapping grant type: {}", getLogPrefix(), grant.getType());
+            TokenClaimsSet tokenClaimsSet = null;
+            if (GrantType.AUTHORIZATION_CODE.equals(grant.getType())) {
+                final AuthorizationCodeGrant codeGrant = (AuthorizationCodeGrant) grant;
+                if (codeGrant.getAuthorizationCode() != null && codeGrant.getAuthorizationCode().getValue() != null) {
+                    try {
+                        final String codeValue = codeGrant.getAuthorizationCode().getValue();
+                        assert codeValue != null;
+                        final AuthorizeCodeClaimsSet authzCodeClaimsSet = AuthorizeCodeClaimsSet.parse(codeValue,
+                                dataSealer);
+                        assert authzCodeClaimsSet != null;
+                        final String jti = authzCodeClaimsSet.getID();
+                        if (jti == null) {
+                            log.warn("{} Invalid contents in the authz code grant: no JTI", getLogPrefix());
+                            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                            return;
+                        }
+                        log.debug("{} Authz code unwrapped {}", getLogPrefix(), authzCodeClaimsSet.serialize());
+                        tokenClaimsSet = authzCodeClaimsSet;
+                        
+                    } catch (final DataSealerException | ParseException e) {
+                        log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
+                        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                        return;
+                    }
+                }
+            } 
+            validateTokenClaimsSet(profileRequestContext, tokenClaimsSet);
+            final OIDCAuthenticationResponseContext oidcResponseContext = getOidcResponseContext();
+            assert oidcResponseContext != null;
+            oidcResponseContext.setAuthorizationGrantClaimsSet(tokenClaimsSet);
+            tokenContext.setPotentialCredentials(tokenClaimsSet.getUserinfoDeliveryClaims());
+            return;
+        }else if (getOpenIDVCITokenRequest().getPreAuthorizedCode() instanceof String  ){
+            // This is pre-authorized grant
+            String code = getOpenIDVCITokenRequest().getPreAuthorizedCode();
+            log.info("{} Unwrapping pre-authorization code: {}", getLogPrefix(), code);
+            
+            Exception e;
+            try {
+                CredentialOfferClaimsSet credentialOffer = CredentialOfferClaimsSet.parse(code, dataSealer);
+                assert credentialOffer != null;
+                final String jti = credentialOffer.getID();
+                if (jti == null) {
+                    log.warn("{} Invalid contents in the pre-authorized grant: no JTI", getLogPrefix());
+                    ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+                    return;
+                }
+                log.debug("{} pre-authorized grant unwrapped {}", getLogPrefix(), credentialOffer.serialize());
+                validateTokenClaimsSet(profileRequestContext, credentialOffer);
+                tokenContext.setCredentialOfferObject(CredentialOfferObject.parse(credentialOffer.getSubject()));
+                setPotentialCredentials(tokenContext);
+                return;
+            } catch (final DataSealerException | JsonProcessingException | com.nimbusds.oauth2.sdk.ParseException | ParseException  e2) {
+                log.warn("{} Unwrapping authz code failed: {}, might be storage based", getLogPrefix(), e2.getMessage());
+            }
+            
+            try {
+                tokenContext.setCredentialOfferObject(credentialOfferCache.getCredentialOffer(code));
+                setPotentialCredentials(tokenContext);
+                credentialOfferCache.removeCredentialOffer(code);
+                return;
+            } catch (IOException | net.minidev.json.parser.ParseException e1) {
+                e = e1;
+            }
+            log.error("{} Validating pre-authorized code {} failed", getLogPrefix(), code, e);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+        }
+        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+
+    }
+
+    private void validateTokenClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext, TokenClaimsSet tokenClaimsSet) {
+        if (tokenClaimsSet == null) {
+            log.warn("{} Grant type not supported", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        if (!tokenClaimsSet.isTimeValid()) {
+            log.warn("{} Token is expired or not net valid", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+        final ClientID clientId = tokenClaimsSet.getClientID();
+        assert clientId != null;
+        final ClientID requestClientId = clientIDLookupStrategy
+                .apply(profileRequestContext.ensureInboundMessageContext());
+        final ClientID inheritedClientId = new ClientID(clientId.getValue()+"/wallet");
+        if (!clientId.equals(requestClientId) && !inheritedClientId.equals(requestClientId)) {
+            log.warn("{} Token issued to client {}, invalid for {}", getLogPrefix(), clientId.getValue(),
+                    requestClientId);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
+    }
+    
+    private void setPotentialCredentials(@Nonnull TokenContext ctx) {
+        ClaimsSet credentialOfferClaims = new ClaimsSet();
+        ctx.getCredentialOfferObject().getPreAuthorizedCredentials().forEach((key, value) -> {
+            try {
+                credentialOfferClaims.setClaim(key, value.serialize());
+            } catch (JsonProcessingException e) {
+                log.warn("{} Error occurred while handling CredentialOfferObject {}", getLogPrefix(), e);
+
+            }
+        });
+        ctx.setPotentialCredentials(credentialOfferClaims);
+    }
+
+
+}
\ No newline at end of file
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAuthorizedCodeGrant.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAuthorizedCodeGrant.java
deleted file mode 100644
index 865d5e6..0000000
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidateAuthorizedCodeGrant.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.geant.shibboleth.plugin.openidvci.profile.impl;
-
-import java.text.ParseException;
-
-import javax.annotation.Nonnull;
-
-import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCITokenRequest;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
-import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.shared.annotation.ParameterName;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-public class ValidateAuthorizedCodeGrant extends AbstractProfileAction {
-
-    /** Class logger. */
-    @Nonnull
-    private Logger log = LoggerFactory.getLogger(ValidateAuthorizedCodeGrant.class);
-
-    /** Data sealer for unwrapping authorization code. */
-    @Nonnull
-    private final DataSealer dataSealer;
-
-    @NonnullBeforeExec
-    private String code;
-
-    /**
-     * Constructor.
-     * 
-     * @param sealer sealer to decrypt/hmac authorize code.
-     */
-    public ValidateAuthorizedCodeGrant(@Nonnull @ParameterName(name = "sealer") final DataSealer sealer) {
-        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        if (profileRequestContext.getInboundMessageContext() == null) {
-            log.error("{} No inbound message context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-        if (profileRequestContext.getInboundMessageContext().getMessage() instanceof OpenIDVCITokenRequest request) {
-            code = request.getCode();
-        }
-        if (code == null) {
-            log.error("{} No authorization code in vci token request", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        log.info("{} Validating authorization code: {}", getLogPrefix(), code);
-        try {
-            TokenContext ctx = profileRequestContext.getInboundMessageContext().ensureSubcontext(TokenContext.class);
-            ctx.setAuthorizeCodeClaimsSet(AuthorizeCodeClaimsSet.parse(code, dataSealer));
-            ctx.setPotentialCredentials(ctx.getAuthorizeCodeClaimsSet().getUserinfoDeliveryClaims());
-            // TODO: Now we are using mix of OIDCAuthenticationResponseContext and flow
-            // specific TokenContext. Make a policy decision on that.
-            OIDCAuthenticationResponseContext oidcRespCtx = profileRequestContext.getOutboundMessageContext()
-                    .ensureSubcontext(OIDCAuthenticationResponseContext.class);
-            oidcRespCtx.setAuthorizationGrantClaimsSet(ctx.getAuthorizeCodeClaimsSet());
-        } catch (ParseException | DataSealerException e) {
-            log.warn("{} Unwrapping authz code failed: {}", getLogPrefix(), e.getMessage());
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-            return;
-        }
-
-    }
-
-}
diff --git a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidatePreAuthorizedCodeGrant.java b/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidatePreAuthorizedCodeGrant.java
deleted file mode 100644
index dbe5880..0000000
--- a/openid-vci-impl/src/main/java/org/geant/shibboleth/plugin/openidvci/profile/impl/ValidatePreAuthorizedCodeGrant.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (c) 2025, GÉANT
- *
- * Licensed 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 org.geant.shibboleth.plugin.openidvci.profile.impl;
-
-import java.io.IOException;
-import java.text.ParseException;
-
-import javax.annotation.Nonnull;
-
-import org.geant.shibboleth.plugin.openidvci.messaging.context.TokenContext;
-import org.geant.shibboleth.plugin.openidvci.messaging.impl.OpenIDVCITokenRequest;
-import org.geant.shibboleth.plugin.openidvci.storage.CredentialOfferCache;
-import org.geant.shibboleth.plugin.openidvci.storage.CredentialOfferObject;
-import org.geant.shibboleth.plugin.openidvci.token.support.CredentialOfferClaimsSet;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
-
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.oidc.profile.core.OidcEventIds;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
-
-public class ValidatePreAuthorizedCodeGrant extends AbstractProfileAction {
-
-    /** Class logger. */
-    @Nonnull
-    private Logger log = LoggerFactory.getLogger(ValidatePreAuthorizedCodeGrant.class);
-
-    @NonnullAfterInit
-    private CredentialOfferCache credentialOfferCache;
-
-    /** Data sealer for unwrapping pre-authorized code. */
-    @NonnullAfterInit
-    private DataSealer dataSealer;
-
-    @NonnullBeforeExec
-    private String code;
-
-    /**
-     * Set the credential offer cache instance to use.
-     * 
-     * @param cache The credential offer cache to set.
-     */
-    public void setCredentialOfferCache(@Nonnull final CredentialOfferCache cache) {
-        checkSetterPreconditions();
-        credentialOfferCache = Constraint.isNotNull(cache, "CredentialOfferCache cannot be null");
-    }
-
-    /**
-     * Set the data sealer instance to use.
-     * 
-     * @param sealer sealer to use
-     */
-    public void setDataSealer(@Nonnull final DataSealer sealer) {
-        ifInitializedThrowUnmodifiabledComponentException();
-        dataSealer = Constraint.isNotNull(sealer, "DataSealer cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-
-        if (dataSealer == null) {
-            throw new ComponentInitializationException("DataSealer cannot be null");
-        }
-        if (credentialOfferCache == null) {
-            throw new ComponentInitializationException("CredentialOfferCache cannot be null");
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        if (profileRequestContext.getInboundMessageContext() == null) {
-            log.error("{} No inbound message context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-        if (profileRequestContext.getInboundMessageContext().getMessage() instanceof OpenIDVCITokenRequest request) {
-            code = request.getPreAuthorizedCode();
-        }
-        if (code == null) {
-            log.error("{} No pre-authorization code in vci token request.", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        log.info("{} Validating pre-authorization code: {}", getLogPrefix(), code);
-        TokenContext ctx = profileRequestContext.getInboundMessageContext().ensureSubcontext(TokenContext.class);
-        Exception e;
-        try {
-            CredentialOfferClaimsSet credentialOffer = CredentialOfferClaimsSet.parse(code, dataSealer);
-            ctx.setCredentialOfferObject(CredentialOfferObject.parse(credentialOffer.getSubject()));
-            setPotentialCredentials(ctx);
-            return;
-        } catch (ParseException | DataSealerException | JsonProcessingException
-                | com.nimbusds.oauth2.sdk.ParseException e1) {
-            // Unable to parse, assuming it is storage based
-            e = e1;
-        }
-        try {
-            ctx.setCredentialOfferObject(credentialOfferCache.getCredentialOffer(code));
-            setPotentialCredentials(ctx);
-            credentialOfferCache.removeCredentialOffer(code);
-            return;
-        } catch (IOException | net.minidev.json.parser.ParseException e1) {
-            e = e1;
-        }
-        log.error("{} Validating pre-authorized code {} failed", getLogPrefix(), code, e);
-        ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-        return;
-
-    }
-
-    private void setPotentialCredentials(@Nonnull TokenContext ctx) {
-        ClaimsSet credentialOfferClaims = new ClaimsSet();
-        ctx.getCredentialOfferObject().getPreAuthorizedCredentials().forEach((key, value) -> {
-            try {
-                credentialOfferClaims.setClaim(key, value.serialize());
-            } catch (JsonProcessingException e) {
-                log.warn("{} Error occurred while handling CredentialOfferObject {}", getLogPrefix(), e);
-
-            }
-        });
-        ctx.setPotentialCredentials(credentialOfferClaims);
-    }
-
-}
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
index fc184d3..6ffc14d 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-beans.xml
@@ -19,22 +19,20 @@
             scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" />
     </constructor-arg>
   </bean>
+  
+  <bean id="UnwrapOrLocateGrant" class="org.geant.shibboleth.plugin.openidvci.profile.impl.UnwrapGrant" scope="prototype"
+        c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
+        p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
+        p:credentialOfferCache-ref="openidvci.CredentialOfferCache"/>
 
   <bean id="ValidateExpectedGrantType" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateExpectedGrantType"
         scope="prototype"/>
 
-  <bean id="ValidateAuthorizedCodeGrant" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateAuthorizedCodeGrant"
-        scope="prototype" c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"/>
-
   <bean id="ValidatePKCE" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidatePKCE" scope="prototype" 
         p:codeVerifierLookupStrategy-ref="RequestCodeVerifierLookupFunction"/>
        
   <bean id="RequestCodeVerifierLookupFunction" class="org.geant.shibboleth.plugin.openidvci.messaging.context.navigate.RequestCodeVerifierLookupFunction" />
 
-  <bean id="ValidatePreAuthorizedCodeGrant" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidatePreAuthorizedCodeGrant"
-        scope="prototype" p:credentialOfferCache-ref="openidvci.CredentialOfferCache"
-        p:dataSealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}" />
-
   <bean id="ValidateTxCode" class="org.geant.shibboleth.plugin.openidvci.profile.impl.ValidateTxCode"
         scope="prototype"/>
         
diff --git a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
index 336ef26..a008b4c 100644
--- a/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
+++ b/openid-vci-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/openid/vci/token/token-flow.xml
@@ -5,12 +5,25 @@
       parent="openid/vci/abstract-api">
 
   <action-state id="InitializeMandatoryContexts">
-    <evaluate expression="InitializeProfileRequestContext"/>
-    <evaluate expression="PopulateMetricContext"/>
-    <evaluate expression="FlowStartPopulateAuditContext"/>
-    <evaluate expression="InitializeOutboundMessageContext"/>
-    <evaluate expression="'proceed'"/>
-    <transition on="proceed" to="DecodeMessage"/>
+    <evaluate expression="InitializeProfileRequestContext" />
+    <evaluate expression="PopulateMetricContext" />
+    <evaluate expression="FlowStartPopulateAuditContext" />
+    <evaluate expression="InitializeOutboundMessageContext" />
+    <evaluate expression="'proceed'" />
+        
+    <transition on="proceed" to="DecodeMessage">
+      <set name="flowScope.transitionAfterDecode" value="'PostDecodeMessage'" />
+    </transition>
+  </action-state>
+
+  <action-state id="PostDecodeMessage">
+    <!-- flow specific implementation to unwrap both grants -->
+    <!--  target it to be closer to mother flow with outcome to use mother flow actions -->  
+    <evaluate expression="UnwrapOrLocateGrant" />
+    <evaluate expression="'proceed'" />
+
+    <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
+    <transition on="proceed" to="DoMetadataLookup" />
   </action-state>
   
   <!-- Authentication subflow happens here. -->
@@ -28,14 +41,12 @@
 
   <action-state id="ValidateCodeFlow">
     <evaluate expression="ValidateExpectedGrantType"/>
-    <evaluate expression="ValidateAuthorizedCodeGrant"/>
     <evaluate expression="'proceed'"/>
     <transition on="proceed" to="PostValidation"/>
   </action-state>
 
   <action-state id="ValidatePreAuthFlow">
     <evaluate expression="ValidateExpectedGrantType"/>
-    <evaluate expression="ValidatePreAuthorizedCodeGrant"/>
     <evaluate expression="ValidateTxCode"/>
     <evaluate expression="'proceed'"/>
     <transition on="proceed" to="PostValidation"/>

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


More information about the commits mailing list