[java-idp-oidc] branch main updated: JOIDC-21 - Use token authentication for OIDC dynamic client registration

Henri Mikkonen henri.mikkonen at iki.fi
Fri Mar 4 13:21:15 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=d61acc04405e04e389cc4fbd58fba189a2ff8538

The following commit(s) were added to refs/heads/main by this push:
     new d61acc04 JOIDC-21 - Use token authentication for OIDC dynamic client registration
d61acc04 is described below

commit d61acc04405e04e389cc4fbd58fba189a2ff8538
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Mar 4 15:18:58 2022 +0200

    JOIDC-21 - Use token authentication for OIDC dynamic client registration
    
    https://shibboleth.atlassian.net/browse/JOIDC-21
    
    Support one-time flag for the access token: if enabled, the token gets revoked
    after it has been used once.
---
 .../op/token/support/RegistrationClaimsSet.java    | 39 ++++++++++++++-
 .../cli/IssueRegistrationAccessTokenArguments.java | 22 +++++++--
 .../profile/impl/IssueRegistrationAccessToken.java | 17 +++++++
 .../impl/ValidateRegistrationAccessToken.java      | 14 ++++++
 ...tRegistrationTokenOnetimeUseLookupFunction.java | 56 ++++++++++++++++++++++
 .../issue-registration-access-token-flow.xml       |  1 +
 6 files changed, 143 insertions(+), 6 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
index 2139be0b..172298ee 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/token/support/RegistrationClaimsSet.java
@@ -25,7 +25,6 @@ import javax.annotation.Nonnull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonSetter;
@@ -82,6 +81,10 @@ public final class RegistrationClaimsSet {
     /** Relying party identifier. */
     @JsonProperty("rp_id")
     private String relyingPartyId;
+    
+    /** Flag to signal one-time use of the token. */
+    @JsonProperty("onetime")
+    private Boolean onetime;
 
     /**
      * Constructor.
@@ -308,6 +311,24 @@ public final class RegistrationClaimsSet {
     public void setRelyingPartyId(final String rpId) {
         this.relyingPartyId = rpId;
     }
+    
+    /**
+     * Get the flag to signal one-time use of the token.
+     * 
+     * @return The flag to signal one-time use of the token.
+     */
+    public boolean isOnetime() {
+        return onetime == null ? false : onetime.booleanValue();
+    }
+
+    /**
+     * Set the flag to signal one-time use of the token.
+     * 
+     * @param flag What to set.
+     */
+    public void setOnetime(final Boolean flag) {
+        this.onetime = flag;
+    }
 
     /**
      * The builder for {@link RegistrationClaimsSet}.
@@ -343,7 +364,10 @@ public final class RegistrationClaimsSet {
         
         /** Relying party identifier. */
         private String relyingPartyId;
-        
+
+        /** Flag to signal one-time use of the token. */
+        private Boolean onetime;
+
         /**
          * Constructor.
          *
@@ -444,6 +468,16 @@ public final class RegistrationClaimsSet {
             return this;
         }
         
+        /**
+         * Set the flag to signal one-time use of the token.
+         * @param flag What to set.
+         * @return The builder instance.
+         */
+        public Builder withOnetime(final Boolean flag) {
+            this.onetime = flag;
+            return this;
+        }
+        
         /**
          * Build the claims set object.
          * @return The claims set object.
@@ -460,6 +494,7 @@ public final class RegistrationClaimsSet {
             claimsSet.setAuthTime(authTime);
             claimsSet.setMetadata(metadata);
             claimsSet.setRelyingPartyId(relyingPartyId);
+            claimsSet.setOnetime(onetime);
             return claimsSet;
         }   
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/cli/IssueRegistrationAccessTokenArguments.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/cli/IssueRegistrationAccessTokenArguments.java
index 7d42422f..b7580230 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/cli/IssueRegistrationAccessTokenArguments.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/cli/IssueRegistrationAccessTokenArguments.java
@@ -38,19 +38,32 @@ public class IssueRegistrationAccessTokenArguments extends AbstractCommandLineAr
     
     /** The URL parameter name for the relying party identifier. */
     public static final String URL_PARAM_RELYING_PARTY_ID = "relyingPartyId";
+    
+    /** The URL parameter name for the one-time flag. */
+    public static final String URL_PARAM_ONE_TIME_TOKEN = "onetime";
 
     /** Metadata policy for the requested OIDC dynamic client registration metadata. */
-    @Parameter(names = {"-m", "--metadataPolicyLocation"}, required = false, description = "Metadata policy location")
+    @Parameter(names = {"-m", "--metadataPolicyLocation"}, required = true, description = "Metadata policy location")
     @Nullable private String metadata;
     
     /** Lifetime for the access token to be issued. */
-    @Parameter(names = {"-l", "--lifetime"}, required = false, description = "Lifetime for the access token")
+    @Parameter(names = {"-l", "--lifetime"}, required = true, description = "Lifetime for the access token")
     @Nullable private String lifetime;
     
     /** Relying party identifier for the access token to be issued. */
-    @Parameter(names = {"-i", "--relyingPartyId"}, required = false, description = "Relying party ID the access token")
+    @Parameter(names = {"-i", "--relyingPartyId"}, required = true, description = "Relying party ID the access token")
     @Nullable private String relyingPartyId;
     
+    /** Flag to signal one-time use of the token. */
+    @Parameter(names = {"-o", "--onetime"}, required = false, description = "Flag to signal one-time use of the token")
+    @Nullable private boolean onetime;
+
+    /**
+     * Constructor.
+     */
+    public IssueRegistrationAccessTokenArguments() {
+        onetime = true;
+    }
     
     /** {@inheritDoc} */
     @Override
@@ -87,7 +100,8 @@ public class IssueRegistrationAccessTokenArguments extends AbstractCommandLineAr
                 .append("&" + URL_PARAM_METADATA_POLICY_LOCATION + "=")
                 .append(URLEncoder.encode(metadata, "UTF-8"))
                 .append("&" + URL_PARAM_RELYING_PARTY_ID + "=")
-                .append(URLEncoder.encode(relyingPartyId, "UTF-8"));
+                .append(URLEncoder.encode(relyingPartyId, "UTF-8"))
+                .append("&" + URL_PARAM_ONE_TIME_TOKEN + "=" + onetime);
         } catch (final UnsupportedEncodingException e) {
             // UTF-8 is a required encoding.
             throw new RuntimeException("URL encoding failed", e);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessToken.java
index c74eeaeb..0c51aa1c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessToken.java
@@ -41,6 +41,7 @@ import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 import com.nimbusds.oauth2.sdk.token.Tokens;
 
 import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultRegistrationTokenLifetimeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultRegistrationTokenOnetimeUseLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultRegistrationTokenRelyingPartyIdLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
 import net.shibboleth.idp.profile.AbstractProfileAction;
@@ -92,6 +93,9 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
     
     /** Lookup function for the relying party identifier. */
     @Nonnull private Function<ProfileRequestContext, String> relyingPartyIdLookupStrategy;
+    
+    /** Lookup function for the flag signaling one-time use of the token. */
+    @Nonnull private Function<ProfileRequestContext, Boolean> onetimeUseLookupStrategy;
 
     /** The identifier generator to use. */
     @Nullable private IdentifierGenerationStrategy idGenerator;
@@ -115,6 +119,7 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
         idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
         tokenLifetimeLookupStrategy = new DefaultRegistrationTokenLifetimeLookupFunction();
         relyingPartyIdLookupStrategy = new DefaultRegistrationTokenRelyingPartyIdLookupFunction();
+        onetimeUseLookupStrategy = new DefaultRegistrationTokenOnetimeUseLookupFunction();
     }
     
     /**
@@ -199,6 +204,17 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
                 "Relying party ID lookup strategy cannot be null");
     }
 
+    /**
+     * Set a lookup strategy for the flag signaling one-time use of the token.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setOnetimeUseLookupStrategy(@Nonnull final Function<ProfileRequestContext, Boolean> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        onetimeUseLookupStrategy = Constraint.isNotNull(strategy, "One-time use lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -300,6 +316,7 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
                 .withMetadata(metadataPolicy)
                 .withExpiration(exp)
                 .withRelyingPartyId(relyingPartyId)
+                .withOnetime(onetimeUseLookupStrategy.apply(profileRequestContext))
                 .build();
         
         //TODO: possible end-user authentication claims:
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
index 2f7b0c6c..2f57507e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationAccessToken.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.util.function.Function;
 
@@ -235,6 +236,19 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
             return;
         }
         log.debug("{} registration access token {} successfully validated", getLogPrefix(), claimsSet.getJti());
+        if (claimsSet.isOnetime()) {
+            //TODO clockskew
+            final boolean revoked = revocationCache.revoke(RevocationCacheContexts.REGISTRATION_ACCESS_TOKEN,
+                    claimsSet.getJti(), Duration.between(Instant.now(), claimsSet.getExpiration()));
+            if (revoked) {
+                log.debug("{} Successfully revoked the one-time token with jti {}", getLogPrefix(),
+                        claimsSet.getJti());
+            } else {
+                log.error("{} Could not revoke a one-time token with jti {}", getLogPrefix(), claimsSet.getJti());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+                return;
+            }
+        }
         relyingPartyContext.setVerified(true);
         relyingPartyContext.setRelyingPartyId(relyingPartyId);
         registrationClaimsContext.setClaimsSet(claimsSet);
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenOnetimeUseLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenOnetimeUseLookupFunction.java
new file mode 100644
index 00000000..e55a2e37
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenOnetimeUseLookupFunction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.logic;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.cli.IssueRegistrationAccessTokenArguments;
+import net.shibboleth.idp.profile.context.SpringRequestContext;
+
+/**
+ * A lookup function that fetches the flag signaling one-time use of the token from the SWF request parameters.
+ * 
+ * The parameter key is {@link IssueRegistrationAccessTokenArguments#URL_PARAM_ONE_TIME_TOKEN}.
+ */
+public class DefaultRegistrationTokenOnetimeUseLookupFunction implements Function<ProfileRequestContext, Boolean> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public Boolean apply(final @Nonnull ProfileRequestContext profileRequestContext) {
+        final SpringRequestContext springRequestContext =
+                profileRequestContext.getSubcontext(SpringRequestContext.class);
+        if (springRequestContext == null) {
+            return null;
+        }
+
+        final RequestContext requestContext = springRequestContext.getRequestContext();
+        if (requestContext == null) {
+            return null;
+        }
+
+        final String result = (String) requestContext.getFlowScope().get(
+                IssueRegistrationAccessTokenArguments.URL_PARAM_ONE_TIME_TOKEN);
+        return Boolean.valueOf(result);
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/oidc/issue-registration-access-token/issue-registration-access-token-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/oidc/issue-registration-access-token/issue-registration-access-token-flow.xml
index 42f2dcd7..4f07ff68 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/oidc/issue-registration-access-token/issue-registration-access-token-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/oidc/issue-registration-access-token/issue-registration-access-token-flow.xml
@@ -10,6 +10,7 @@
         <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('tokenLifetime'))" result="flowScope.tokenLifetime" />
         <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('metadataPolicyLocation'))" result="flowScope.metadataPolicyLocation" />
         <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('relyingPartyId'))" result="flowScope.relyingPartyId" />
+        <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('onetime'))" result="flowScope.onetime" />
     </on-start>
     
     <action-state id="InitializeProfileRequestContext">

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


More information about the commits mailing list