[java-plugin-shibd-oidc] branch main updated: JSHIBDOIDC-12 - Add standard attribute extraction

Codeberg noreply at shibboleth.net
Fri Feb 20 13:26:58 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.

View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/80b76868cf1b5631c1e69b3d33979fe50f8eb399

The following commit(s) were added to refs/heads/main by this push:
     new 80b7686  JSHIBDOIDC-12 - Add standard attribute extraction
80b7686 is described below

commit 80b76868cf1b5631c1e69b3d33979fe50f8eb399
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Feb 20 13:26:47 2026 +0000

    JSHIBDOIDC-12 - Add standard attribute extraction
    
     - Allow for standard attribute extraction for issuer, authn_time, and
    acr value.
    
    https://shibboleth.atlassian.net/browse/JSHIBDOIDC-12
---
 .../idp/flows/sp/consumer/oidc/oidc-beans.xml      |  12 +-
 .../net/shibboleth/sp/service/agent/postconfig.xml |   3 +-
 .../shibboleth/idp/module/conf/sp/oidc.properties  |  10 ++
 .../sp/oidc/profile/impl/ExtractOIDCClaims.java    |  29 ++++-
 .../profile/impl/StandardExtractionStrategy.java   | 135 +++++++++++++++++++++
 5 files changed, 185 insertions(+), 4 deletions(-)

diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
index 3f3fce0..d45183c 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -537,7 +537,7 @@
         <ref bean="UserInfoTokenRequiredClaimsValidator" />
         <ref bean="SubMatchesIDTokenClaimValidator" />
         <ref bean="IssuerClaimsValidator" />
-       <!--   <ref bean="AudienceClaimsValidator" /> -->
+       <!--   TODO <ref bean="AudienceClaimsValidator" /> -->
     </util:list>
 
     <bean id="UserInfoTokenRequiredClaimsValidator"
@@ -556,7 +556,15 @@
         </property>
     </bean>
     
-    <bean id="ExtractOIDCClaims" class="net.shibboleth.sp.oidc.profile.impl.ExtractOIDCClaims" scope="prototype"/>
+    <bean id="ExtractOIDCClaims" class="net.shibboleth.sp.oidc.profile.impl.ExtractOIDCClaims" scope="prototype"
+        p:acceptUnfilteredAttributes="%{sp.oidc.acceptUnfilteredAttributes:false}">
+        <property name="standardExtractionStrategy">
+            <bean class="net.shibboleth.sp.oidc.profile.impl.StandardExtractionStrategy"
+                p:issuerAttributeId="%{sp.oidc.issuerAttributeId:Shib-Identity-Provider}"
+                p:authnInstantAttributeId="%{sp.oidc.authnInstantAttributeId:Shib-Authentication-Instant}"
+                p:authnContextClassRefAttributeId="%{sp.oidc.authnContextClassRefAttributeId:Shib-AuthnContext-Class}"/>
+        </property>
+    </bean>
     
     <bean id="PrepareAgentResponse" class="net.shibboleth.sp.oidc.profile.impl.PrepareAgentResponse" scope="prototype"
      p:attributeContextLookupStrategy-ref="shibboleth.ChildLookup.AttributeContextFromEndUserClaimsContext"
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index e7c0037..b8c4888 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -99,7 +99,8 @@
         p:dpopProofSignatureValidationConfiguration="#{getObject('DPoPSignatureValidationConfiguration')}"
         p:dpopProofNonceGenerator="#{getObject('DefaultOAuth2DPoPNonceGenerator')}"
         p:strictScopeValidation="%{sp.oidc.strictScopeValidation:false}"
-        p:clientCredential="#{%{sp.oidc.discoveryRequired:false} == true ? {null} : getObject('shibboleth.oidc.DefaultCredential')}"/>
+        p:clientCredential="#{%{sp.oidc.discoveryRequired:false} == true ? {null} : getObject('shibboleth.oidc.DefaultCredential')}"
+        p:extractStandardAttributes="%{sp.oidc.extractStandardAttributes:false}"/>
 
    <bean id="OIDC.SSO" parent="AbstractOIDCSSOProfile" lazy-init="true"
         class="net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration"        
diff --git a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
index d86ad55..4581381 100644
--- a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
+++ b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
@@ -48,6 +48,16 @@
 # Should the ACR value in the response be compared to those in the request?
 #sp.oidc.idtoken.validateAcrValue = true
 
+# Set true to accept any decided attributes without a filtering rule
+#sp.oidc.acceptUnfilteredAttributes = false
+
+# "Standard" attribute extraction from OIDC id_token claims
+#sp.oidc.extractStandardAttributes = false
+# Legacy names defaulted into agents
+#sp.oidc.issuerAttributeId = Shib-Identity-Provider
+#sp.oidc.authnInstantAttributeId = Shib-Authentication-Instant
+#sp.oidc.authnContextClassRefAttributeId = Shib-AuthnContext-Class
+
 # Each protocol plugin will contain an order priority controlling
 # relative order of use when handling requests, lower is "earlier".
 #sp.oidc.relativeOrder = 1
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
index 3383b9c..3d4c4c9 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
@@ -89,6 +89,9 @@ public class ExtractOIDCClaims extends AbstractApplicationAction {
     /** Strategy used to create {@link AttributeContext} to hold results. */
     @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextCreationStrategy;
     
+    /** Strategy for pulling out "standard" data from the id_token. */
+    @Nullable private Function<EndUserClaimsContext,Collection<IdPAttribute>> standardExtractionStrategy;
+    
     /** Context containing the claims(s) to process. */
     @NonnullBeforeExec private EndUserClaimsContext endUserClaimsContext;
     
@@ -200,6 +203,17 @@ public class ExtractOIDCClaims extends AbstractApplicationAction {
         
         acceptUnfilteredAttributes = flag;
     }
+    
+    /**
+     * Set the strategy function to call for "standard" attribute extraction, if enabled.
+     * 
+     * @param strategy strategy function
+     */
+    public void setStandardExtractionStrategy(
+            @Nullable final Function<EndUserClaimsContext,Collection<IdPAttribute>> strategy) {
+        checkSetterPreconditions();
+        standardExtractionStrategy = strategy;
+    }
 
     
     /** {@inheritDoc} */
@@ -257,7 +271,20 @@ public class ExtractOIDCClaims extends AbstractApplicationAction {
         
         //TODO custom extraction, attribute resolution
         
-        // TODO Extract standard attributes. We might want to extract additional authentication information, not sure yet
+        // Extract standard attributes.
+        if (standardExtractionStrategy != null && profileConfiguration.isExtractStandardAttributes(profileRequestContext)) {
+            log.debug("{} Extracting standard attributes", getLogPrefix());
+            assert standardExtractionStrategy != null;
+            final Collection<IdPAttribute> standards = standardExtractionStrategy.apply(endUserClaimsContext);
+            if (standards != null && !standards.isEmpty()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("{} Extracted standard attributes: {}", getLogPrefix(),
+                            standards.stream().map(IdPAttribute::getId).collect(Collectors.toUnmodifiableList()));
+                }
+                
+                IdPAttributeSupport.withMapMergeDuplicates(accumulator, standards);
+            }
+        }
         
         
         final Function<ProfileRequestContext,Collection<IdPAttribute>> aes =
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java
new file mode 100644
index 0000000..f173b29
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.sp.oidc.profile.impl;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.oidc.profile.context.EndUserClaimsContext;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Function to perform "standard" extraction of data into attributes.
+ */
+public class StandardExtractionStrategy implements Function<EndUserClaimsContext, Collection<IdPAttribute>> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(StandardExtractionStrategy.class);
+    
+    /** Attribute ID holding issuer. */
+    @Nullable private String issuerAttributeId;
+    
+    /** Attribute ID holding authentication instant. */
+    @Nullable private String authnInstantAttributeId;
+    
+    /** Attribute ID holding AuthnContext class ref. */
+    @Nullable private String authnContextClassRefAttributeId;
+    
+    /**
+     * Set {@link IdPAttribute} ID for the 'iss' claim.
+     * 
+     * @param id attribute ID to use, null to omit
+     */
+    public void setIssuerAttributeId(@Nullable final String id) {
+        issuerAttributeId = StringSupport.trimOrNull(id);
+    }
+
+    /**
+     * Set {@link IdPAttribute} ID for the 'auth_time' claim.
+     * 
+     * @param id attribute ID to use, null to omit
+     */
+    public void setAuthnInstantAttributeId(@Nullable final String id) {
+        authnInstantAttributeId = StringSupport.trimOrNull(id);
+    }
+
+    /**
+     * Set {@link IdPAttribute} ID for 'acr' claim.
+     * 
+     * @param id attribute ID to use, null to omit
+     */
+    public void setAuthnContextClassRefAttributeId(@Nullable final String id) {
+        authnContextClassRefAttributeId = StringSupport.trimOrNull(id);
+    }
+    
+    @Nullable public Collection<IdPAttribute> apply(@Nullable final EndUserClaimsContext claimsContext) {
+        
+        final JWTClaimsSet idTokenClaims = claimsContext != null ? claimsContext.getUnprocessedIdTokenClaims() :  null;
+        if (idTokenClaims == null) {
+            return null;
+        }
+        
+        final Collection<IdPAttribute> attributes = new ArrayList<>();
+        
+        if (issuerAttributeId != null) {
+            final String issuer = idTokenClaims.getIssuer();
+            if (issuer != null) {
+                assert issuerAttributeId != null;
+                final IdPAttribute attr = new IdPAttribute(issuerAttributeId);
+                attr.setValues(CollectionSupport.singletonList(new StringAttributeValue(issuer)));
+                attributes.add(attr);
+            }
+        }
+        
+        if (authnInstantAttributeId != null) {
+            try {
+                final Date authTime = idTokenClaims.getDateClaim(IDTokenClaimsSet.AUTH_TIME_CLAIM_NAME);
+                if (authTime!= null) {
+                    assert authnInstantAttributeId != null;
+                    final IdPAttribute attr = new IdPAttribute(authnInstantAttributeId);
+                    attr.setValues(CollectionSupport.singletonList(new DateTimeAttributeValue(authTime.toInstant())));
+                    attributes.add(attr);
+                }
+            } catch (final ParseException e) {
+                log.debug("Unable to process auth_time claim for standard attribute extraction", e);
+            }
+            
+        }
+        
+        if (authnContextClassRefAttributeId != null) {           
+            try {
+                final String acrClaim = idTokenClaims.getStringClaim(IDTokenClaimsSet.ACR_CLAIM_NAME);
+                if (acrClaim != null) {
+                    assert authnContextClassRefAttributeId != null;
+                    final IdPAttribute attr = new IdPAttribute(authnContextClassRefAttributeId);
+                    attr.setValues(CollectionSupport.singletonList(new StringAttributeValue(acrClaim)));
+                    attributes.add(attr);
+                }
+            } catch (final ParseException e) {
+                log.debug("Unable to process ACR claim for standard attribute extraction", e);
+            }           
+        }        
+        
+        return attributes;
+        
+    }
+
+}

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


More information about the commits mailing list