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

Scott Cantor cantor.2 at osu.edu
Tue Mar 15 12:28:35 UTC 2022


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

scantor 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=a6306e74bea2103be443f664e3725977008db7ea

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

commit a6306e74bea2103be443f664e3725977008db7ea
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Mar 15 08:21:55 2022 -0400

    JOIDC-21 - Use token authentication for OIDC dynamic client registration
    
    https://shibboleth.atlassian.net/browse/JOIDC-21
    
    Modify parameter names.
    Allow either of token policy or ID to be absent.
    Support client ID specification and replacement of registration.
---
 .../op/token/support/RegistrationClaimsSet.java    |  38 +--
 .../cli/IssueRegistrationAccessTokenArguments.java | 122 +++++---
 ...nitializeRegistrationMetadataPolicyContext.java |  20 +-
 .../profile/impl/IssueRegistrationAccessToken.java | 338 ++++++++++++++++-----
 .../op/profile/impl/StoreClientInformation.java    |   8 +-
 .../impl/ValidateRegistrationAccessToken.java      |  22 +-
 .../impl/ValidateRegistrationRequestMetadata.java  |  24 +-
 ...efaultMetadataPolicyCriteriaLookupFunction.java |  13 +-
 ...efaultMetadataPolicyLocationLookupFunction.java |  57 ----
 .../DefaultMetadataPolicyMergingStrategy.java      |  18 +-
 ...ultRegistrationTokenLifetimeLookupFunction.java |  62 ----
 ...tRegistrationTokenOnetimeUseLookupFunction.java |  56 ----
 ...istrationTokenRelyingPartyIdLookupFunction.java |  55 ----
 .../issue-registration-access-token-beans.xml      |  15 +-
 .../issue-registration-access-token-flow.xml       |   7 +-
 .../idp/flows/oidc/register/register-beans.xml     |  15 +-
 .../javax.servlet.ServletContainerInitializer      |   2 +-
 .../flow/IssueRegistrationAccessTokenFlowTest.java |   4 +-
 .../oidc/op/profile/flow/RegistrationFlowTest.java |  12 +-
 .../impl/IssueRegistrationAccessTokenTest.java     | 101 +++++-
 20 files changed, 527 insertions(+), 462 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 2c732127..b197ca33 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
@@ -93,9 +93,9 @@ public final class RegistrationClaimsSet {
     @JsonProperty("client_id")
     @Nullable @NotEmpty private String clientId;
 
-    /** Flag to signal one-time use of the token. */
-    @JsonProperty("onetime")
-    @Nullable private Boolean onetime;
+    /** Flag to signal replacement is allowed. */
+    @JsonProperty("replacement")
+    @Nullable private Boolean replacement;
 
     /**
      * Constructor.
@@ -346,21 +346,21 @@ public final class RegistrationClaimsSet {
     }
 
     /**
-     * Get the flag to signal one-time use of the token.
+     * Get the flag to signal replacement is allowed.
      * 
-     * @return The flag to signal one-time use of the token.
+     * @return true iff replacement is allowed
      */
-    public boolean isOnetime() {
-        return onetime == null ? false : onetime.booleanValue();
+    public boolean isReplacement() {
+        return replacement == null ? false : replacement.booleanValue();
     }
 
     /**
-     * Set the flag to signal one-time use of the token.
+     * Set the flag to signal replacement is allowed.
      * 
-     * @param flag What to set.
+     * @param flag flag to set
      */
-    public void setOnetime(@Nullable final Boolean flag) {
-        onetime = flag;
+    public void setReplacement(@Nullable final Boolean flag) {
+        replacement = flag;
     }
 
     /**
@@ -401,8 +401,8 @@ public final class RegistrationClaimsSet {
         /** Client identifier. */
         @Nullable @NotEmpty private String clientId;
 
-        /** Flag to signal one-time use of the token. */
-        @Nullable private Boolean onetime;
+        /** Flag to signal replacement use of the token. */
+        @Nullable private Boolean replacement;
 
         /**
          * Constructor.
@@ -515,12 +515,12 @@ public final class RegistrationClaimsSet {
         }
 
         /**
-         * Set the flag to signal one-time use of the token.
-         * @param flag What to set.
-         * @return The builder instance.
+         * Set the flag to signal replacement use of the token.
+         * @param flag What to set
+         * @return The builder instance
          */
-        public Builder withOnetime(@Nullable final Boolean flag) {
-            onetime = flag;
+        public Builder withReplacement(@Nullable final Boolean flag) {
+            replacement = flag;
             return this;
         }
         
@@ -541,7 +541,7 @@ public final class RegistrationClaimsSet {
             claimsSet.setMetadata(metadata);
             claimsSet.setRelyingPartyId(relyingPartyId);
             claimsSet.setClientId(clientId);
-            claimsSet.setOnetime(onetime);
+            claimsSet.setReplacement(replacement);
             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 25367534..63c7d81d 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
@@ -34,60 +34,57 @@ import net.shibboleth.utilities.java.support.primitive.StringSupport;
 public class IssueRegistrationAccessTokenArguments extends AbstractCommandLineArguments {
     
     /** The URL parameter name for the metadata policy location. */
-    public static final String URL_PARAM_METADATA_POLICY_LOCATION = "metadataPolicyLocation";
+    public static final String URL_PARAM_POLICY_LOCATION = "policyLocation";
     
     /** The URL parameter name for the access token lifetime. */
     public static final String URL_PARAM_LIFETIME = "tokenLifetime";
     
     /** 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";
+    public static final String URL_PARAM_POLICY_ID = "policyId";
+
+    /** The URL parameter name for the client identifier. */
+    public static final String URL_PARAM_CLIENT_ID = "clientId";
 
-    /** Metadata policy for the requested OIDC dynamic client registration metadata. */
-    @Parameter(names = {"-m", "--metadataPolicyLocation"}, required = true, description = "Metadata policy location")
-    @Nullable private String metadata;
+    /** The URL parameter name for the replacement flag. */
+    public static final String URL_PARAM_REPLACEMENT = "replacement";
+
+    /** Metadata policy to embed in the token. */
+    @Parameter(names = {"-loc", "--policyLocation"}, required = false, description = "Metadata policy location")
+    @Nullable private String policyLocation;
     
     /** Lifetime for the access token to be issued. */
-    @Parameter(names = {"-l", "--lifetime"}, required = true, description = "Lifetime for the access token")
+    @Parameter(names = {"-l", "--lifetime"}, required = false, 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 = true, description = "Relying party ID for the access token")
-    @Nullable private String relyingPartyId;
-    
+    /** Metadata policy identifier for the access token to be issued. */
+    @Parameter(names = {"-id", "--policyId"}, required = false, description = "Metadata policy ID")
+    @Nullable private String policyId;
+
+    /** Requested client identifier for the access token to be issued. */
+    @Parameter(names = {"-c", "--clientId"}, required = false, description = "Client ID to request in the access token")
+    @Nullable private String clientId;
+
     /** 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 String onetime;
+    @Parameter(names = {"-r", "--replacement"}, required = false,
+            description = "Flag to request the ability to re-register the same client ID for the life of the token")
+    @Nullable private boolean replacement;
 
     /** Username to be used in the HTTP-Basic authentication. */
-    @Parameter(names = {"-user", "--username"}, required = false, description = "Username to be used in HTTP-Basic Auth")
+    @Parameter(names = {"-u", "--username"}, required = false, description = "Username to be used in HTTP-Basic Auth")
     @Nullable private String username;
 
     /** Password to be used in the HTTP-Basic authentication. */
-    @Parameter(names = {"-pwd", "--password"}, required = false, password = true, 
+    @Parameter(names = {"-p", "--password"}, required = false, password = true,
             description = "Password to be used in HTTP-Basic Auth")
     @Nullable private String password;
-
-    /**
-     * Constructor.
-     */
-    public IssueRegistrationAccessTokenArguments() {
-        onetime = "true";
-    }
     
     /** {@inheritDoc} */
     @Override
     public void validate() {
-        if (metadata == null) {
-            throw new IllegalArgumentException("No metadata spefified");
-        }
-        if (lifetime == null) {
-            throw new IllegalArgumentException("No lifetime specified");
-        }
-        if (relyingPartyId == null) {
-            throw new IllegalArgumentException("No relyingPartyId specified");
+        if (policyLocation == null && policyId == null) {
+            throw new IllegalArgumentException("One of policyLocation or policyId is required");
+        } else if (clientId == null && replacement) {
+            throw new IllegalArgumentException("Enabling replacement requires specifying a client ID");
         }
     }
 
@@ -99,26 +96,57 @@ public class IssueRegistrationAccessTokenArguments extends AbstractCommandLineAr
             builder.append("/profile/admin/oidc/issue-registration-access-token");
         }
 
-        if (builder.toString().contains("?")) {
-            builder.append('&');
-        } else {
-            builder.append('?');
-        }
-
         try {
-            builder
-                .append(URL_PARAM_LIFETIME + "=")
-                .append(URLEncoder.encode(lifetime, "UTF-8"))
-                .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("&" + URL_PARAM_ONE_TIME_TOKEN + "=" + "true".equalsIgnoreCase(onetime));
+            if (policyLocation != null) {
+                appendSeparator(builder)
+                    .append(URL_PARAM_POLICY_LOCATION + "=")
+                    .append(URLEncoder.encode(policyLocation, "UTF-8"));
+            }
+            
+            if (policyId != null) {
+                appendSeparator(builder)
+                    .append(URL_PARAM_POLICY_ID + "=")
+                    .append(URLEncoder.encode(policyId, "UTF-8"));
+            }
+
+            if (lifetime != null) {
+                builder
+                    .append("&" + URL_PARAM_LIFETIME + "=")
+                    .append(URLEncoder.encode(lifetime, "UTF-8"));
+            }
+            
+            if (clientId != null) {
+                builder
+                    .append("&" + URL_PARAM_CLIENT_ID + "=")
+                    .append(URLEncoder.encode(clientId, "UTF-8"));
+                
+                if (replacement) {
+                    builder.append("&" + URL_PARAM_REPLACEMENT + "=" + replacement);
+                }
+            }
         } catch (final UnsupportedEncodingException e) {
             // UTF-8 is a required encoding.
             throw new RuntimeException("URL encoding failed", e);
         }
 
+        return builder;
+     }
+    
+    /**
+     * Append the proper parameter separator to the builder.
+     * 
+     * @param builder input builder
+     * 
+     * @return the input
+     */
+    @Nonnull private StringBuilder appendSeparator(@Nonnull final StringBuilder builder) {
+        
+        if (builder.toString().contains("?")) {
+            builder.append('&');
+        } else {
+            builder.append('?');
+        }
+        
         return builder;
     }
 
@@ -134,4 +162,4 @@ public class IssueRegistrationAccessTokenArguments extends AbstractCommandLineAr
         final String rawHeader = username + ":" + password;
         return "Basic " + Base64.getEncoder().encodeToString(rawHeader.getBytes(StandardCharsets.UTF_8));
     }
-}
\ No newline at end of file
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
index e16faf96..29c97a6e 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/InitializeRegistrationMetadataPolicyContext.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -67,7 +68,7 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
         registrationPolicyContextCreationStrategy;
     
     /** The strategy used for merging profile and token based metadata policies. */
-    @NonnullAfterInit private Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+    @NonnullAfterInit private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
         Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
     
     /** The strategy used for validating token and merged metadata policies. */
@@ -133,8 +134,8 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
      * 
      * @param strategy What to set.
      */
-    public void setMetadataPolicyMergingStrategy(@Nonnull final Function<Pair<Map<String, MetadataPolicy>,
-            Map<String, MetadataPolicy>>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
+    public void setMetadataPolicyMergingStrategy(@Nonnull final BiFunction<Map<String,MetadataPolicy>,
+            Map<String,MetadataPolicy>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         metadataPolicyMergingStrategy = Constraint.isNotNull(strategy,
@@ -162,7 +163,7 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
 
         metadataPolicyContext = registrationPolicyContextCreationStrategy.apply(profileRequestContext);
         if (metadataPolicyContext == null) {
-            log.error("{} The registration metadata policy context could not be created, invalid profile context",
+            log.error("{} Registration metadata policy context could not be created, invalid profile context",
                     getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return false;
@@ -182,27 +183,28 @@ public class InitializeRegistrationMetadataPolicyContext extends AbstractProfile
                 tokenMetadataPolicyLookupStrategy.apply(profileRequestContext);
         
         if (!metadataPolicyValidationStrategy.test(tokenMetadataPolicy)) {
-            log.warn("{} The token metadata policy is not valid", getLogPrefix());
+            log.warn("{} Metadata policy in token is invalid", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return;            
         }
         
         final Pair<Map<String, MetadataPolicy>, Boolean> mergedResult =
-                metadataPolicyMergingStrategy.apply(new Pair<>(profileMetadataPolicy, tokenMetadataPolicy));
+                metadataPolicyMergingStrategy.apply(profileMetadataPolicy, tokenMetadataPolicy);
         
         if (!mergedResult.getSecond()) {
-            log.warn("{} The metadata policies from profile and token could not be merged", getLogPrefix());
+            log.warn("{} Metadata policies from profile and token could not be merged", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return;
         }
         
         final Map<String, MetadataPolicy> mergedPolicy = mergedResult.getFirst();
         if (!metadataPolicyValidationStrategy.test(mergedPolicy)) {
-            log.warn("{} The merged metadata policy is not valid", getLogPrefix());
+            log.warn("{} Merged metadata policy is invalid", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return;
         }
         
         metadataPolicyContext.setMetadataPolicy(mergedPolicy);
     }
-}
+    
+}
\ No newline at end of file
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 deb8e141..4f50cc88 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
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
 import java.time.Duration;
 import java.time.Instant;
+import java.time.format.DateTimeParseException;
 import java.util.Map;
 import java.util.function.Function;
 
@@ -31,7 +32,6 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.webflow.execution.RequestContext;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -41,19 +41,22 @@ import com.nimbusds.oauth2.sdk.token.AccessToken;
 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.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.SubjectContext;
+import net.shibboleth.idp.plugin.oidc.op.cli.IssueRegistrationAccessTokenArguments;
 import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
 import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.idp.profile.context.SpringRequestContext;
 import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
+import net.shibboleth.idp.profile.function.SpringFlowScopeLookupFunction;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
+import net.shibboleth.utilities.java.support.security.AccessControlService;
 import net.shibboleth.utilities.java.support.security.DataSealer;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 import net.shibboleth.utilities.java.support.security.IdentifierGenerationStrategy;
@@ -79,25 +82,43 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
     @NonnullAfterInit private DataSealer dataSealer;
 
     /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
-    @Nonnull private Function<ProfileRequestContext, IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
 
     /** JSON object mapper. */
     @NonnullAfterInit private ObjectMapper objectMapper;
-    
+
+    /** Access control service. */
+    @NonnullAfterInit private AccessControlService accessControlService;
+
+    /** Name of access control policy governing policyLocation acceptance. */
+    @Nullable @NotEmpty private String policyLocationPolicyName;
+
+    /** Name of access control policy governing policyId acceptance. */
+    @Nullable @NotEmpty private String policyIdPolicyName;
+
+    /** Name of access control policy governing clientId acceptance. */
+    @Nullable @NotEmpty private String clientIdPolicyName;
+
     /** Lookup function for the metadata policy. */
-    @NonnullAfterInit private Function<ProfileRequestContext, Map<String,MetadataPolicy>> metadataPolicyLookupStrategy;
+    @NonnullAfterInit private Function<ProfileRequestContext,Map<String,MetadataPolicy>> metadataPolicyLookupStrategy;
 
     /** Lookup function for the token lifetime. */
-    @Nonnull private Function<ProfileRequestContext, Duration> tokenLifetimeLookupStrategy;
+    @Nonnull private Function<ProfileRequestContext,String> tokenLifetimeLookupStrategy;
     
     /** Lookup function for the token issuer. */
-    @NonnullAfterInit private Function<ProfileRequestContext, String> issuerLookupStrategy;
-    
-    /** 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;
+    @NonnullAfterInit private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+    /** Lookup function for the policy location. */
+    @Nonnull private Function<ProfileRequestContext,String> policyLocationLookupStrategy;
+
+    /** Lookup function for the policy identifier. */
+    @Nonnull private Function<ProfileRequestContext,String> policyIdLookupStrategy;
+
+    /** Lookup function for the client identifier. */
+    @Nonnull private Function<ProfileRequestContext,String> clientIdLookupStrategy;
+
+    /** Lookup function for the flag signaling replacement use of the token. */
+    @Nonnull private Function<ProfileRequestContext,String> replacementLookupStrategy;
 
     /** The identifier generator to use. */
     @Nullable private IdentifierGenerationStrategy idGenerator;
@@ -108,11 +129,20 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
     /** The token issuer. */
     @Nonnull private String issuer;
     
-    /** The relying party identifier. */
-    @Nullable private String relyingPartyId;
+    /** The policy location. */
+    @Nullable private String policyLocation;
+
+    /** The policy identifier. */
+    @Nullable private String policyId;
+
+    /** The client identifier. */
+    @Nullable private String clientId;
+
+    /** The token lifetime. */
+    @Nullable private Duration defaultTokenLifetime;
 
     /** The token lifetime. */
-    private Duration tokenLifetime;
+    @Nullable private Duration tokenLifetime;
     
     /**
      * Constructor.
@@ -120,9 +150,18 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
     public IssueRegistrationAccessToken() {
         idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
         issuerLookupStrategy = new ResponderIdLookupFunction();
-        tokenLifetimeLookupStrategy = new DefaultRegistrationTokenLifetimeLookupFunction();
-        relyingPartyIdLookupStrategy = new DefaultRegistrationTokenRelyingPartyIdLookupFunction();
-        onetimeUseLookupStrategy = new DefaultRegistrationTokenOnetimeUseLookupFunction();
+        tokenLifetimeLookupStrategy =
+                new SpringFlowScopeLookupFunction(IssueRegistrationAccessTokenArguments.URL_PARAM_LIFETIME);
+        policyLocationLookupStrategy =
+                new SpringFlowScopeLookupFunction(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_LOCATION);
+        policyIdLookupStrategy =
+                new SpringFlowScopeLookupFunction(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID);
+        clientIdLookupStrategy =
+                new SpringFlowScopeLookupFunction(IssueRegistrationAccessTokenArguments.URL_PARAM_CLIENT_ID);
+        replacementLookupStrategy =
+                new SpringFlowScopeLookupFunction(IssueRegistrationAccessTokenArguments.URL_PARAM_REPLACEMENT);
+        
+        defaultTokenLifetime = Duration.ofDays(1);
     }
     
     /**
@@ -147,19 +186,41 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
         objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
     }
 
+    /**
+     * Set the {@link AccessControlService} to use.
+     * 
+     * @param acs service to use
+     */
+    public void setAccessControlService(@Nonnull final AccessControlService acs) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        accessControlService = Constraint.isNotNull(acs, "AccessControlService cannot be null");
+    }
+
     /**
      * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
      * 
      * @param strategy lookup strategy
      */
     public void setIdentifierGeneratorLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, IdentifierGenerationStrategy> strategy) {
+            @Nonnull final Function<ProfileRequestContext,IdentifierGenerationStrategy> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         idGeneratorLookupStrategy =
                 Constraint.isNotNull(strategy, "IdentifierGenerationStrategy lookup strategy cannot be null");
     }
 
+    /**
+     * Set a lookup strategy for the token issuer.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+    }
+
     /**
      * Set a lookup strategy for the metadata policy.
      * 
@@ -178,21 +239,21 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
      * 
      * @param strategy lookup strategy
      */
-    public void setTokenLifetimeLookupStrategy(@Nonnull final Function<ProfileRequestContext, Duration> strategy) {
+    public void setTokenLifetimeLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
         tokenLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Token lifetime lookup strategy cannot be null");
     }
 
     /**
-     * Set a lookup strategy for the token issuer.
+     * Set a lookup strategy for the metadata policy location.
      * 
      * @param strategy lookup strategy
      */
-    public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+    public void setPolicyLocationLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-        issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+        policyLocationLookupStrategy = Constraint.isNotNull(strategy, "Policy location lookup strategy cannot be null");
     }
 
     /**
@@ -200,22 +261,76 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
      * 
      * @param strategy lookup strategy
      */
-    public void setRelyingPartyIdLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+    public void setPolicyIdLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        policyIdLookupStrategy = Constraint.isNotNull(strategy, "Policy ID lookup strategy cannot be null");
+    }
+
+    /**
+     * Set a lookup strategy for the client identifier.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setClientIdLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-        relyingPartyIdLookupStrategy = Constraint.isNotNull(strategy,
-                "Relying party ID lookup strategy cannot be null");
+        clientIdLookupStrategy = Constraint.isNotNull(strategy, "Client ID lookup strategy cannot be null");
     }
 
     /**
-     * Set a lookup strategy for the flag signaling one-time use of the token.
+     * Set a lookup strategy for the flag signaling registration replacement is allowed.
      * 
      * @param strategy lookup strategy
      */
-    public void setOnetimeUseLookupStrategy(@Nonnull final Function<ProfileRequestContext, Boolean> strategy) {
+    public void setReplacementLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-        onetimeUseLookupStrategy = Constraint.isNotNull(strategy, "One-time use lookup strategy cannot be null");
+        replacementLookupStrategy = Constraint.isNotNull(strategy, "Replacement lookup strategy cannot be null");
+    }
+
+    /**
+     * Set an explicit policy name to apply governing policyLocation usage.
+     * 
+     * @param name  policy name
+     */
+    public void setPolicyLocationPolicyName(@Nullable @NotEmpty final String name) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        policyLocationPolicyName = StringSupport.trimOrNull(name);
+    }
+
+    /**
+     * Set an explicit policy name to apply governing policyId usage.
+     * 
+     * @param name  policy name
+     */
+    public void setPolicyIdPolicyName(@Nullable @NotEmpty final String name) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        policyIdPolicyName = StringSupport.trimOrNull(name);
+    }
+
+    /**
+     * Set an explicit policy name to apply governing clientId usage.
+     * 
+     * @param name  policy name
+     */
+    public void setClientIdPolicyName(@Nullable @NotEmpty final String name) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clientIdPolicyName = StringSupport.trimOrNull(name);
+    }
+    
+    /**
+     * Set the default token lifetime.
+     * 
+     * @param lifetime
+     */
+    public void setDefaultTokenLifetime(@Nonnull final Duration lifetime) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        defaultTokenLifetime = Constraint.isNotNull(lifetime, "Default token lifetime cannot be null");
     }
 
     /** {@inheritDoc} */
@@ -231,31 +346,23 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
             throw new ComponentInitializationException("ObjectMapper cannot be null");
         }
 
+        if (accessControlService == null) {
+            throw new ComponentInitializationException("AccessControlService cannot be null");
+        }
+        
         if (metadataPolicyLookupStrategy == null) {
             throw new ComponentInitializationException("Metadata policy lookup strategy cannot be null");
         }
     }
 
     /** {@inheritDoc} */
-    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         
         if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
 
-        final SpringRequestContext springRequestContext =
-                profileRequestContext.getSubcontext(SpringRequestContext.class);
-        if (springRequestContext == null) {
-            log.warn("{} Spring request context not found in profile request context", getLogPrefix());
-            return false;
-        }
-
-        final RequestContext requestContext = springRequestContext.getRequestContext();
-        if (requestContext == null) {
-            log.warn("{} Web Flow request context not found in Spring request context", getLogPrefix());
-            return false;
-        }
-
         idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
         if (idGenerator == null) {
             log.error("{} No identifier generation strategy", getLogPrefix());
@@ -263,57 +370,71 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
             return false;
         }
 
-        metadataPolicy = metadataPolicyLookupStrategy.apply(profileRequestContext);
-        // null is not allowed - empty metadata policy is
-        if (metadataPolicy == null) {
-            log.warn("{} No metadata policy could be resolved", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
-            return false;
-        }
-
         issuer = issuerLookupStrategy.apply(profileRequestContext);
         if (issuer == null) {
             log.warn("{} No issuer could be resolved", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return false;
         }
-        
-        relyingPartyId = relyingPartyIdLookupStrategy.apply(profileRequestContext);
-        if (relyingPartyId == null) {
-            log.warn("{} No relying party ID could be resolved", getLogPrefix());
+
+        policyLocation = policyLocationLookupStrategy.apply(profileRequestContext);
+        policyId = policyIdLookupStrategy.apply(profileRequestContext);
+        metadataPolicy = metadataPolicyLookupStrategy.apply(profileRequestContext);
+        if (metadataPolicy == null && policyId == null) {
+            log.warn("{} No metadata policy or policy ID could be resolved", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return false;
         }
         
-        tokenLifetime = tokenLifetimeLookupStrategy.apply(profileRequestContext);
-        if (tokenLifetime == null) {
-            log.warn("{} No token lifetime could be resolved", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
-            return false;
+        clientId = clientIdLookupStrategy.apply(profileRequestContext);
+        
+        final String lifetimeString = tokenLifetimeLookupStrategy.apply(profileRequestContext);
+        if (lifetimeString != null) {
+            try {
+                tokenLifetime = Duration.parse(lifetimeString);
+                if (tokenLifetime.compareTo(defaultTokenLifetime) > 0) {
+                    log.warn("Requested token lifetime greater than default, lowering to default", getLogPrefix());
+                    tokenLifetime = defaultTokenLifetime;
+                }
+            } catch (final DateTimeParseException e) {
+                log.warn("{} Token lifetime was not in a supported format", getLogPrefix(), e);
+            }
+        } else {
+            log.debug("{} No token lifetime specified, using default", getLogPrefix());
+            tokenLifetime = defaultTokenLifetime;
         }
         
         return true;
     }
-
+    
     /** {@inheritDoc} */
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!checkAccess(profileRequestContext)) {
+            return;
+        }
         
         final String id = idGenerator.generateIdentifier();
         
         final Instant now = Instant.now();
         final Instant exp = now.plus(tokenLifetime);
 
-        final RegistrationClaimsSet claimsSet = new RegistrationClaimsSet.Builder(id)
+        final RegistrationClaimsSet.Builder builder = new RegistrationClaimsSet.Builder(id)
                 .withIssuer(issuer)
                 .withIssuedAt(now)
-                .withMetadata(metadataPolicy)
                 .withExpiration(exp)
-                .withRelyingPartyId(relyingPartyId)
-                .withOnetime(onetimeUseLookupStrategy.apply(profileRequestContext))
-                .build();
+                .withMetadata(metadataPolicy)
+                .withRelyingPartyId(policyId);
+        
+        if (clientId != null) {
+            builder.withClientId(clientId)
+                .withReplacement(Boolean.valueOf(replacementLookupStrategy.apply(profileRequestContext)));
+        }
+
+        addAuthenticationClaims(profileRequestContext, builder);
         
-        //TODO: possible end-user authentication claims:
-        //via Builder: .withAcr .withAuthTime .withPrincipal
+        final RegistrationClaimsSet claimsSet = builder.build();
         
         final AccessToken accessToken;
         
@@ -339,4 +460,73 @@ public class IssueRegistrationAccessToken extends AbstractProfileAction {
         profileRequestContext.setOutboundMessageContext(mc);
     }
 
-}
+    /**
+     * Check access policies.
+     * 
+     * @param profileRequestContext current profile request context
+     * 
+     * @return true iff checks pass
+     */
+    private boolean checkAccess(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (policyId != null) {
+            if (policyIdPolicyName == null) {
+                log.warn("{} No policy name govering policyId usage, disallowing access", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            } else if (!accessControlService.getInstance(policyIdPolicyName).checkAccess(getHttpServletRequest(),
+                    "read", policyId)) {
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            }
+        }
+
+        if (policyLocation != null) {
+            if (policyLocationPolicyName == null) {
+                log.warn("{} No policy name govering policyLocation usage, disallowing access", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            } else if (!accessControlService.getInstance(policyLocationPolicyName).checkAccess(getHttpServletRequest(),
+                    "read", policyLocation)) {
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            }
+        }
+
+        if (clientId != null) {
+            if (clientIdPolicyName == null) {
+                log.warn("{} No policy name govering clientId usage, disallowing access", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            } else if (!accessControlService.getInstance(clientIdPolicyName).checkAccess(getHttpServletRequest(),
+                    "write", clientId)) {
+                ActionSupport.buildEvent(profileRequestContext, EventIds.ACCESS_DENIED);
+                return false;
+            }
+        }
+        
+        return true;
+    }
+    
+    /**
+     * Decorate the token with authentication-related claims.
+     * 
+     * @param profileRequestContext profile request context
+     * @param builder claims set builder
+     */
+    private void addAuthenticationClaims(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final RegistrationClaimsSet.Builder builder) {
+        
+        final AuthenticationContext authnContext = profileRequestContext.getSubcontext(AuthenticationContext.class);
+        if (authnContext != null) {
+            if (authnContext.getAuthenticationResult() != null) {
+                builder.withAuthTime(authnContext.getAuthenticationResult().getAuthenticationInstant());
+            }
+        }
+        
+        final SubjectContext subjectContext = profileRequestContext.getSubcontext(SubjectContext.class);
+        if (subjectContext != null && subjectContext.getPrincipalName() != null) {
+            builder.withPrincipal(subjectContext.getPrincipalName());
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
index 70d9f9dc..a60d2949 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/StoreClientInformation.java
@@ -190,12 +190,8 @@ public class StoreClientInformation extends AbstractProfileAction {
         Duration lifetime = registrationValidityPeriodStrategy != null ?
                 registrationValidityPeriodStrategy.apply(profileRequestContext) : null;
         
-        final boolean replace;
-        if (registrationTokenCtx != null) {
-            replace = !registrationTokenCtx.getClaimsSet().isOnetime();
-        } else {
-            replace = false;
-        }
+        final boolean replace = registrationTokenCtx != null ?
+                registrationTokenCtx.getClaimsSet().isReplacement() : false;
         
         log.debug("{} Storing client information (replace = {})", getLogPrefix(), replace);
         
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 e30f802f..df89ceae 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
@@ -21,6 +21,7 @@ import java.time.Instant;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
@@ -41,6 +42,7 @@ import net.shibboleth.idp.plugin.oidc.op.token.support.RegistrationClaimsSet;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
@@ -74,10 +76,10 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
     @NonnullAfterInit private ObjectMapper objectMapper;
     
     /** The relying party context to operate on. */
-    private RelyingPartyContext relyingPartyContext;
+    @Nullable private RelyingPartyContext relyingPartyContext;
 
     /** The registration access token to be validated. */
-    private String accessToken;
+    @Nullable @NotEmpty private String accessToken;
     
     /**
      * Constructor.
@@ -203,7 +205,7 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
             return;
         }
 
-        log.debug("{} registration access token decoded into {}", getLogPrefix(), claimsSet);
+        log.debug("{} Registration access token decoded into {}", getLogPrefix(), claimsSet);
 
         if (Instant.now().isAfter(claimsSet.getExpiration())) {
             log.error("{} Registration access token exp is in the past {}", getLogPrefix(), claimsSet.getExpiration());
@@ -217,11 +219,14 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
             return;
         }
         final String relyingPartyId = claimsSet.getRelyingPartyId();
-        if (relyingPartyId == null) {
-            log.error("{} Registration access token {} didn't contain relying party identifier", getLogPrefix(),
+        if (relyingPartyId != null) {
+            log.debug("{} Registration access token {} carries relying party identifier {}", getLogPrefix(),
+                    claimsSet.getJti(), relyingPartyId);
+            relyingPartyContext.setVerified(true);
+            relyingPartyContext.setRelyingPartyId(relyingPartyId);
+        } else {
+            log.debug("{} Registration access token {} carries no relying party identifier", getLogPrefix(),
                     claimsSet.getJti());
-            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
-            return;
         }
         
         log.debug("{} Registration access token {} successfully validated", getLogPrefix(), claimsSet.getJti());
@@ -236,9 +241,6 @@ public class ValidateRegistrationAccessToken extends AbstractOIDCRequestAction<O
         }
         
         registrationClaimsContext.setClaimsSet(claimsSet);
-        
-        relyingPartyContext.setVerified(true);
-        relyingPartyContext.setRelyingPartyId(relyingPartyId);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
index 6bd7f14e..50169532 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateRegistrationRequestMetadata.java
@@ -18,6 +18,7 @@
 package net.shibboleth.idp.plugin.oidc.op.profile.impl;
 
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
@@ -52,15 +53,14 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
 public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
 
     /** Class logger. */
-    @Nonnull
-    private final Logger log = LoggerFactory.getLogger(ValidateRegistrationRequestMetadata.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateRegistrationRequestMetadata.class);
     
     /** Strategy that will return {@link OIDCClientRegistrationMetadataPolicyContext}. */
     @Nonnull private Function<MessageContext, OIDCClientRegistrationMetadataPolicyContext>
         registrationMetadataPolicyContextLookupStrategy;
     
     /** Function used for enforcing the metadata policy. */
-    @Nonnull private Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> metadataPolicyEnforcer;
+    @Nonnull private BiFunction<Object,MetadataPolicy,Pair<Object,Boolean>> metadataPolicyEnforcer;
 
     /** The OIDCClientRegistrationRequest to validate. */
     @Nullable private OIDCClientRegistrationRequest request;
@@ -76,7 +76,7 @@ public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
      */
     public ValidateRegistrationRequestMetadata() {
         registrationMetadataPolicyContextLookupStrategy = 
-                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class, false);
+                new ChildContextLookup<>(OIDCClientRegistrationMetadataPolicyContext.class);
 
         metadataPolicyEnforcer = new DefaultMetadataPolicyEnforcer();
     }
@@ -100,7 +100,7 @@ public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
      * @param function Function used for enforcing the metadata policy.
      */
     public void setMetadataPolicyEnforcer(
-            @Nonnull final Function<Pair<Object, MetadataPolicy>, Pair<Object, Boolean>> function) {
+            @Nonnull final BiFunction<Object,MetadataPolicy,Pair<Object,Boolean>> function) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
         
         metadataPolicyEnforcer = Constraint.isNotNull(function, "The metadata policy enforcer cannot be null");
@@ -149,7 +149,7 @@ public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
             registrationMetadataPolicyContext.setPolicyEnforcedMetadata(request.getOIDCClientMetadata());
             return;
         }
-        log.debug("{} Metadata policy used for the request validation: {}", getLogPrefix(), metadataPolicy);
+        log.debug("{} Metadata policy used for request validation: {}", getLogPrefix(), metadataPolicy);
 
         boolean compliant = true;
 
@@ -158,16 +158,14 @@ public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
         for (final String claim : metadataPolicy.keySet()) {
             final MetadataPolicy policy = metadataPolicy.get(claim);
             final Object value = requestMetadata.get(claim);
-            log.debug("{} The claim {} set in policy included in the request: {}", getLogPrefix(), claim,
+            log.debug("{} Claim {} set in policy included in the request: {}", getLogPrefix(), claim,
                     value == null);
-            final Pair<Object, MetadataPolicy> candidate = new Pair<>(value, policy);
-            final Pair<Object, Boolean> result = metadataPolicyEnforcer.apply(candidate);
-                
+            final Pair<Object,Boolean> result = metadataPolicyEnforcer.apply(value, policy);
             if (!result.getSecond()) {
-                log.warn("{} the metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+                log.warn("{} Metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
                 compliant = false;
             } else {
-                log.trace("{} validation result is OK for claim {}", getLogPrefix(), claim);
+                log.trace("{} Validation result is OK for claim {}", getLogPrefix(), claim);
                 requestMetadata.put(claim, result.getFirst());
             }
         }
@@ -187,4 +185,4 @@ public class ValidateRegistrationRequestMetadata extends AbstractProfileAction {
         }
     }
 
-}
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyCriteriaLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyCriteriaLookupFunction.java
index 1cb076c2..49d1a8a3 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyCriteriaLookupFunction.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyCriteriaLookupFunction.java
@@ -26,14 +26,17 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import net.shibboleth.idp.plugin.oidc.op.cli.IssueRegistrationAccessTokenArguments;
+import net.shibboleth.idp.profile.function.SpringFlowScopeLookupFunction;
 import net.shibboleth.oidc.metadata.criterion.ResourceLocationCriterion;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 
 /**
  * A function returning a {@link CriteriaSet} which contains the metadata policy document location as {@link
- * ResourceLocationCriterion}. The value is fetched from the SWF request parameters, using {@link
- * DefaultMetadataPolicyLocationLookupFunction}.
+ * ResourceLocationCriterion}.
+ * 
+ * <p>The value is fetched from the SWF flow scope.</p>
  */
 public class DefaultMetadataPolicyCriteriaLookupFunction implements Function<ProfileRequestContext, CriteriaSet> {
 
@@ -43,13 +46,15 @@ public class DefaultMetadataPolicyCriteriaLookupFunction implements Function<Pro
     /** {@inheritDoc} */
     @Override @Nullable
     public CriteriaSet apply(@Nonnull final ProfileRequestContext profileRequestContext) {
-        final String location = new DefaultMetadataPolicyLocationLookupFunction().apply(profileRequestContext);
+        final String location = new SpringFlowScopeLookupFunction(
+                IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_LOCATION).apply(profileRequestContext);
         if (StringSupport.trimOrNull(location) == null) {
-            log.warn("Could not find the location for building the criteria set, returning null");
+            log.debug("Could not find a policy location for building the criteria set, returning null");
             return null;
         }
         log.trace("Found a location {} to be included in the criteria set", location);
         final ResourceLocationCriterion criterion = new ResourceLocationCriterion(location);
         return new CriteriaSet(criterion);
     }
+
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyLocationLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyLocationLookupFunction.java
deleted file mode 100644
index 7275c072..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyLocationLookupFunction.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- * A lookup function that fetches the metadata policy document location from the SWF request parameters.
- * 
- * The parameter key is {@link IssueRegistrationAccessTokenArguments#URL_PARAM_METADATA_POLICY_LOCATION}.
- */
-public class DefaultMetadataPolicyLocationLookupFunction implements Function<ProfileRequestContext, String> {
-
-    /** {@inheritDoc} */
-    @Override @Nullable
-    public String apply(@Nonnull final 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 metadataPolicyUrl = (String) requestContext.getFlowScope().get(
-                IssueRegistrationAccessTokenArguments.URL_PARAM_METADATA_POLICY_LOCATION);
-        return StringSupport.trimOrNull(metadataPolicyUrl);
-    }
-}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
index 67d499b0..ca5faa4c 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultMetadataPolicyMergingStrategy.java
@@ -20,7 +20,7 @@ package net.shibboleth.idp.plugin.oidc.op.profile.logic;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
+import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -67,21 +67,17 @@ import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
  * in the policies were compliant.
  */
 public class DefaultMetadataPolicyMergingStrategy implements
-    Function<Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>>,
+    BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
         Pair<Map<String, MetadataPolicy>, Boolean>> {
 
     /** Class logger. */
-    @Nonnull
-    private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyMergingStrategy.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultMetadataPolicyMergingStrategy.class);
 
     /** {@inheritDoc} */
-    @Override
     @Nonnull
-    public Pair<Map<String, MetadataPolicy>, Boolean> apply(
-            @Nullable final Pair<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>> input) {
-        final Map<String, MetadataPolicy> first = input.getFirst();
-        final Map<String, MetadataPolicy> second = input.getSecond();
-        if (first == null) {
+    public Pair<Map<String, MetadataPolicy>, Boolean> apply(@Nullable final Map<String,MetadataPolicy> first,
+            @Nullable final Map<String,MetadataPolicy> second) {
+        if (first == null || first.isEmpty()) {
             return new Pair<>(second, Boolean.TRUE);
         } else if (second == null || second.isEmpty()) {
             return new Pair<>(first, Boolean.TRUE);
@@ -101,4 +97,4 @@ public class DefaultMetadataPolicyMergingStrategy implements
         return new Pair<>(result, Boolean.valueOf(valid));
     }
 
-}
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenLifetimeLookupFunction.java
deleted file mode 100644
index dc8a0fcd..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenLifetimeLookupFunction.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.time.Duration;
-import java.time.format.DateTimeParseException;
-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 token lifetime from the SWF request parameters.
- * 
- * The parameter key is {@link IssueRegistrationAccessTokenArguments#URL_PARAM_LIFETIME}.
- */
-public class DefaultRegistrationTokenLifetimeLookupFunction implements Function<ProfileRequestContext, Duration> {
-
-    /** {@inheritDoc} */
-    @Override @Nullable
-    public Duration 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 lifetime = (String) requestContext.getFlowScope().get(
-                IssueRegistrationAccessTokenArguments.URL_PARAM_LIFETIME);
-        try {
-            return lifetime == null ? null : Duration.parse(lifetime);
-        } catch (final DateTimeParseException e) {
-            return null;
-        }
-    }
-}
\ No newline at end of file
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
deleted file mode 100644
index e55a2e37..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenOnetimeUseLookupFunction.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenRelyingPartyIdLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenRelyingPartyIdLookupFunction.java
deleted file mode 100644
index 7be97fce..00000000
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/DefaultRegistrationTokenRelyingPartyIdLookupFunction.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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 relying party identifier from the SWF request parameters.
- * 
- * The parameter key is {@link IssueRegistrationAccessTokenArguments#URL_PARAM_RELYING_PARTY_ID}.
- */
-public class DefaultRegistrationTokenRelyingPartyIdLookupFunction implements Function<ProfileRequestContext, String> {
-
-    /** {@inheritDoc} */
-    @Override @Nullable
-    public String 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;
-        }
-
-        return (String) requestContext.getFlowScope().get(
-                IssueRegistrationAccessTokenArguments.URL_PARAM_RELYING_PARTY_ID);
-    }
-}
\ 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-beans.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-beans.xml
index e0ae4819..c723bb0b 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-beans.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-beans.xml
@@ -21,7 +21,7 @@
 
     <!-- Default operation/resource suppliers for access checks. -->
     
-    <bean id="shibboleth.AdminOperationLookupStrategy" parent="shibboleth.Functions.Constant" c:target="issue" />
+    <bean id="shibboleth.AdminOperationLookupStrategy" parent="shibboleth.Functions.Constant" c:target="execute" />
     
     <bean id="shibboleth.AdminResourceLookupStrategy" parent="shibboleth.Functions.Constant"
         c:target="oidc/issue-registration-access-token" />
@@ -30,10 +30,15 @@
 
     <bean id="IssueRegistrationAccessToken"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.IssueRegistrationAccessToken" scope="prototype"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest"
         p:httpServletResponse-ref="shibboleth.HttpServletResponse"
         p:sealer-ref="#{'%{idp.oidc.dynreg.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
         p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper"
-        p:metadataPolicyLookupStrategy-ref="%{idp.oidc.admin.registration.lookup.policy:shibboleth.oidc.admin.DefaultMetadataPolicyLookupStrategy}" />
+        p:accessControlService-ref="shibboleth.AccessControlService"
+        p:metadataPolicyLookupStrategy-ref="%{idp.oidc.admin.registration.lookup.policy:shibboleth.oidc.admin.DefaultMetadataPolicyLookupStrategy}"
+        p:policyLocationPolicyName="%{idp.oidc.admin.registration.policyLocationPolicy:AccessByIPAddress}"
+        p:policyIdPolicyName="%{idp.oidc.admin.registration.policyIdPolicy:AccessByIPAddress}"
+        p:clientIdPolicyName="%{idp.oidc.admin.registration.clientIdPolicy:AccessByIPAddress}" />
 
     <bean id="shibboleth.oidc.admin.DefaultMetadataPolicyLookupStrategy"
         class="net.shibboleth.oidc.profile.config.navigate.ResolverBasedRegistrationMetadataPolicyLookupFunction"
@@ -66,7 +71,8 @@
 
     <bean id="shibboleth.oidc.admin.MetadataPolicyIdentifierStrategy" parent="shibboleth.Functions.Compose">
         <constructor-arg name="g">
-            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyLocationLookupFunction" />
+            <bean class="net.shibboleth.idp.profile.function.SpringFlowScopeLookupFunction"
+                c:_0="T(net.shibboleth.idp.plugin.oidc.op.cli.IssueRegistrationAccessTokenArguments).URL_PARAM_POLICY_LOCATION" />
         </constructor-arg>
         <constructor-arg name="f">
             <bean parent="shibboleth.Functions.Expression"
@@ -79,8 +85,7 @@
         id="shibboleth.oidc.admin.BaseCacheBuilderSpec" abstract="true"
         p:fetchStrategy-ref="shibboleth.oidc.admin.MetadataPolicyFetchingStrategy"
         p:criteriaToIdentifierStrategy-ref="shibboleth.oidc.admin.MetadataPolicyIdentifierStrategy"
-        p:identifierExtractionStrategy-ref="shibboleth.oidc.admin.MetadataPolicyIdentifierStrategy"
-       />
+        p:identifierExtractionStrategy-ref="shibboleth.oidc.admin.MetadataPolicyIdentifierStrategy" />
 
     <bean id="shibboleth.oidc.admin.MetadataPolicyFetchingStrategy"
         class="net.shibboleth.oidc.metadata.policy.impl.MetadataPolicyViaLocationFetchingStrategy"
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 0c66ba19..f0a99342 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
@@ -8,9 +8,10 @@
     <on-start>
         <!-- Extract the parameters in case authentication disturbs the URL. -->
         <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" />
+        <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('policyLocation'))" result="flowScope.policyLocation" />
+        <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('policyId'))" result="flowScope.policyId" />
+        <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('clientId'))" result="flowScope.clientId" />
+        <evaluate expression="T(net.shibboleth.utilities.java.support.primitive.StringSupport).trimOrNull(externalContext.getNativeRequest().getParameter('replacement'))" result="flowScope.replacement" />
     </on-start>
     
     <action-state id="InitializeProfileRequestContext">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
index 0058a9e2..b9c52bf8 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
@@ -30,6 +30,10 @@
         </constructor-arg>
     </bean>
 
+    <bean id="InitializeRelyingPartyContext"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext"
+        scope="prototype" />
+
     <bean id="ValidateRegistrationAccessToken"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationAccessToken" scope="prototype"
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
@@ -43,17 +47,13 @@
     <bean id="SelectProfileConfiguration"
         class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype" />
 
-    <bean id="ValidateRegistrationRequestMetadata"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
-        
     <bean id="InitializeRegistrationMetadataPolicyContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRegistrationMetadataPolicyContext"
         scope="prototype" />
 
-    <bean id="InitializeRelyingPartyContext"
-        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeUnverifiedRelyingPartyContext"
-        scope="prototype" />
-
+    <bean id="ValidateRegistrationRequestMetadata"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateRegistrationRequestMetadata" scope="prototype" />
+        
     <bean id="CheckRedirectURIs"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.CheckRedirectURIs"
         scope="prototype"
@@ -232,5 +232,4 @@
         </property>
     </bean>    
 
-
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/idp-oidc-extension-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
index 7614630a..bb154f19 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -1 +1 @@
-net.shibboleth.idp.plugin.oidc.op.servlet.RegisterFilterServletContextInitializer
\ No newline at end of file
+#net.shibboleth.idp.plugin.oidc.op.servlet.RegisterFilterServletContextInitializer
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
index 821259f8..c6acc0bd 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IssueRegistrationAccessTokenFlowTest.java
@@ -97,11 +97,11 @@ public class IssueRegistrationAccessTokenFlowTest extends AbstractOidcFlowTest {
                     URLEncoder.encode(lifetime, "UTF-8"));
         }
         if (metadataLocation != null) {
-            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_METADATA_POLICY_LOCATION,
+            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_LOCATION,
                     metadataLocation);
         }
         if (relyingPartyId != null) {
-            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_RELYING_PARTY_ID, relyingPartyId);
+            request.addParameter(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID, relyingPartyId);
         }
     }
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
index fca9528e..db9418a2 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
@@ -25,8 +25,6 @@ import java.time.Instant;
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
@@ -152,7 +150,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         clientId = "https://example.org";
         
         setJsonRequest("POST", buildRequestMessage(redirectUri));
-        request.addHeader("Authorization", buildRegistrationAccessToken(false, "[\"https://example.org/cb\"]")
+        request.addHeader("Authorization", buildRegistrationAccessToken(true, "[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
         assertSuccessfulResponse(flowExecutor.launchExecution(FLOW_ID, null, externalContext), clientId);
     }
@@ -171,7 +169,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         initializeThreadLocals();
         
         setJsonRequest("POST", buildRequestMessage(redirectUri));
-        request.addHeader("Authorization", buildRegistrationAccessToken(false, "[\"https://example.org/cb\"]")
+        request.addHeader("Authorization", buildRegistrationAccessToken(true, "[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
         assertSuccessfulResponse(flowExecutor.launchExecution(FLOW_ID, null, externalContext), null);
     }
@@ -190,7 +188,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         initializeThreadLocals();
 
         setJsonRequest("POST", buildRequestMessage(redirectUri));
-        request.addHeader("Authorization", buildRegistrationAccessToken(true, "[\"https://example.org/cb\"]")
+        request.addHeader("Authorization", buildRegistrationAccessToken(false, "[\"https://example.org/cb\"]")
                 .toAuthorizationHeader());
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.SERVER_ERROR_CODE);
@@ -285,7 +283,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals(storedMetadata.getPolicyURIEntries(), metadata.getPolicyURIEntries());
     }
 
-    protected BearerAccessToken buildRegistrationAccessToken(final boolean onetime, final String redirectUriSubset,
+    protected BearerAccessToken buildRegistrationAccessToken(final boolean replacement, final String redirectUriSubset,
             final String... additionalPolicyClaims) throws Exception {
         final StringBuilder metadata = new StringBuilder();
         if (additionalPolicyClaims != null) {
@@ -311,7 +309,7 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
                 "\"jti\":\"" + idGenerator.generateIdentifier() + "\"," + 
                 "\"rp_id\":\"" + rpId + "\"," + 
                 (clientId != null ? ("\"client_id\":\"" + clientId + "\",") : "") +
-                "\"onetime\":" + Boolean.toString(onetime) + "," +
+                "\"replacement\":" + Boolean.toString(replacement) + "," +
                 "\"metadata\":" + (metadata.length() == 0 ? "null" : "{" + metadata.toString()) + "}" +
                 "}";
         return new BearerAccessToken(BaseOIDCResponseActionTest.initializeDataSealer().wrap(json,
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessTokenTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessTokenTest.java
index ee83f818..75081777 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessTokenTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/IssueRegistrationAccessTokenTest.java
@@ -23,11 +23,14 @@ import java.time.Instant;
 import java.util.HashMap;
 import java.util.Map;
 
+import javax.servlet.ServletRequest;
+
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.springframework.webflow.execution.Event;
 import org.springframework.webflow.execution.RequestContext;
 import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -45,6 +48,8 @@ import net.shibboleth.idp.profile.testing.RequestContextBuilder;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.logic.FunctionSupport;
+import net.shibboleth.utilities.java.support.security.AccessControl;
+import net.shibboleth.utilities.java.support.security.AccessControlService;
 import net.shibboleth.utilities.java.support.security.DataSealer;
 import net.shibboleth.utilities.java.support.security.DataSealerException;
 
@@ -63,12 +68,21 @@ public class IssueRegistrationAccessTokenTest {
     
     private ObjectMapper objectMapper;
     
+    private AccessControlService accessControlService;
+    
     private String issuer = "mockIssuer";
     
     private String relyingPartyId = "tokenRpId";
-    
+
+    private String clientId = "mockClient";
+
     private String lifetime = "P1D";
     
+    @BeforeClass
+    public void initOnce() {
+        accessControlService = new MockAccessControlService();
+    }
+    
     @BeforeMethod
     public void init() throws ComponentInitializationException, NoSuchAlgorithmException {
         dataSealer = BaseOIDCResponseActionTest.initializeDataSealer();
@@ -77,6 +91,10 @@ public class IssueRegistrationAccessTokenTest {
         action.setObjectMapper(objectMapper);
         action.setMetadataPolicyLookupStrategy(FunctionSupport.constant(defaultMetadataPolicy()));
         action.setSealer(dataSealer);
+        action.setAccessControlService(accessControlService);
+        action.setPolicyLocationPolicyName("policyLocationPolicy");
+        action.setPolicyIdPolicyName("policyIdPolicy");
+        action.setClientIdPolicyName("clientIdPolicy");
         action.setIssuerLookupStrategy(FunctionSupport.constant(issuer));
         action.initialize();
         requestCtx = new RequestContextBuilder().buildRequestContext();
@@ -93,6 +111,7 @@ public class IssueRegistrationAccessTokenTest {
     public void testNoSealer() throws ComponentInitializationException {
         action = new IssueRegistrationAccessToken();
         action.setObjectMapper(objectMapper);
+        action.setAccessControlService(accessControlService);
         action.setMetadataPolicyLookupStrategy(FunctionSupport.constant(defaultMetadataPolicy()));
         action.setIssuerLookupStrategy(FunctionSupport.constant(issuer));
         action.initialize();
@@ -102,6 +121,7 @@ public class IssueRegistrationAccessTokenTest {
     public void testNoObjectMapper() throws ComponentInitializationException {
         action = new IssueRegistrationAccessToken();
         action.setSealer(dataSealer);
+        action.setAccessControlService(accessControlService);
         action.setMetadataPolicyLookupStrategy(FunctionSupport.constant(defaultMetadataPolicy()));
         action.setIssuerLookupStrategy(FunctionSupport.constant(issuer));
         action.initialize();
@@ -112,6 +132,7 @@ public class IssueRegistrationAccessTokenTest {
         action = new IssueRegistrationAccessToken();
         action.setSealer(dataSealer);
         action.setObjectMapper(objectMapper);
+        action.setAccessControlService(accessControlService);
         action.setIssuerLookupStrategy(FunctionSupport.constant(issuer));
         action.initialize();
     }
@@ -121,6 +142,7 @@ public class IssueRegistrationAccessTokenTest {
         action = new IssueRegistrationAccessToken();
         action.setSealer(dataSealer);
         action.setObjectMapper(objectMapper);
+        action.setAccessControlService(accessControlService);
         action.setMetadataPolicyLookupStrategy(FunctionSupport.constant(null));
         action.setIssuerLookupStrategy(FunctionSupport.constant(issuer));
         action.initialize();
@@ -130,28 +152,43 @@ public class IssueRegistrationAccessTokenTest {
     }
     
     @Test
-    public void testNoTokenLifetime() {
-        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_RELYING_PARTY_ID,
+    public void testNoTokenLifetime() throws DataSealerException, JsonMappingException, JsonProcessingException {
+        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID,
                 relyingPartyId);
         final Event event = action.execute(requestCtx);
-        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+        ActionTestingSupport.assertProceedEvent(event);
+        validateToken(relyingPartyId, null);
     }
 
     @Test
-    public void testNoRelyingPartyId() {
+    public void testSuccessNoPolicyId() throws DataSealerException, JsonMappingException, JsonProcessingException {
         requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_LIFETIME, lifetime);
         final Event event = action.execute(requestCtx);
-        ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
+        ActionTestingSupport.assertProceedEvent(event);
+        validateToken(null, null);
     }
 
     @Test
-    public void testSuccess() throws DataSealerException, JsonMappingException, JsonProcessingException {
+    public void testSuccessWithPolicyId() throws DataSealerException, JsonMappingException, JsonProcessingException {
         requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_LIFETIME, lifetime);
-        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_RELYING_PARTY_ID,
-                relyingPartyId);
-        final Instant start = Instant.now();
+        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID, relyingPartyId);
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+        validateToken(relyingPartyId, null);
+    }
+
+    @Test
+    public void testSuccessWithClientIdPolicyId() throws DataSealerException, JsonMappingException, JsonProcessingException {
+        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_LIFETIME, lifetime);
+        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_POLICY_ID, relyingPartyId);
+        requestCtx.getFlowScope().put(IssueRegistrationAccessTokenArguments.URL_PARAM_CLIENT_ID, clientId);
         final Event event = action.execute(requestCtx);
         ActionTestingSupport.assertProceedEvent(event);
+        validateToken(relyingPartyId, clientId);
+    }
+
+    protected void validateToken(final String policyId, final String clientId) throws DataSealerException, JsonMappingException, JsonProcessingException {
+        final Instant start = Instant.now();
         final Object rawMessage = prc.getOutboundMessageContext().getMessage();
         Assert.assertNotNull(rawMessage);
         Assert.assertTrue(rawMessage instanceof AccessTokenResponse);
@@ -164,7 +201,8 @@ public class IssueRegistrationAccessTokenTest {
         Assert.assertEquals(claimsSet.getKeyType(), "rt");
         Assert.assertNotNull(claimsSet.getJti());
         Assert.assertEquals(claimsSet.getIssuer(), issuer);
-        Assert.assertEquals(claimsSet.getRelyingPartyId(), relyingPartyId);
+        Assert.assertEquals(claimsSet.getRelyingPartyId(), policyId);
+        Assert.assertEquals(claimsSet.getClientId(), clientId);
         final Map<String, MetadataPolicy> tokenPolicy = claimsSet.getMetadata();
         Assert.assertNotNull(tokenPolicy);
         Assert.assertEquals(tokenPolicy.size(), 1);
@@ -174,11 +212,48 @@ public class IssueRegistrationAccessTokenTest {
         assertInstantWithSkew(claimsSet.getIssuedAt(), start);
         assertInstantWithSkew(claimsSet.getExpiration(), start.plus(Duration.ofDays(1)));
     }
-
+    
     protected void assertInstantWithSkew(final Instant instant, final Instant target) {
         final Duration skew = Duration.ofSeconds(5);
         Assert.assertTrue(instant.isAfter(target.minus(skew)));
         Assert.assertTrue(instant.isBefore(target.plus(skew)));
     }
 
-}
+    /** Mock service for ACL checks. */
+    private class MockAccessControlService implements AccessControlService {
+
+        public boolean isInitialized() {
+            return true;
+        }
+
+        public void initialize() throws ComponentInitializationException {
+            
+        }
+
+        public String getId() {
+            return "mockACS";
+        }
+
+        public AccessControl getInstance(final String name) {
+            return new AccessControl() {
+                public boolean checkAccess(final ServletRequest request, final String operation,
+                        final String resource) {
+                    if ("read".equals(operation)) {
+                        if ("policyIdPolicy".equals(name) && relyingPartyId.equals(resource)) {
+                            return true;
+                        }
+                        
+                        return false;
+                    } else if ("write".equals(operation)) {
+                        if ("clientIdPolicy".equals(name) && clientId.equals(resource)) {
+                            return true;
+                        }
+                    }
+                    return false;
+                }
+            };
+        }
+        
+    }
+    
+}
\ 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