[java-plugin-shibd-oidc] branch main updated: Add ui_locales handler

Phil Smart philip.smart at jisc.ac.uk
Wed Oct 15 10:56:08 UTC 2025


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

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

View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-oidc.git;a=commit;h=556b6d93a3baf71fced8cc1b81db31bb3e75f08d

The following commit(s) were added to refs/heads/main by this push:
     new 556b6d9  Add ui_locales handler
556b6d9 is described below

commit 556b6d93a3baf71fced8cc1b81db31bb3e75f08d
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Oct 15 11:56:05 2025 +0100

    Add ui_locales handler
    
     - takes them only from the input DDF
---
 .../sp/oidc/profile/OIDCInitiatorConstants.java    |  3 +
 .../idp/flows/sp/initiator/oidc/oidc-beans.xml     |  6 ++
 .../sp/oidc/flows/OIDCAuthenticationFlowTest.java  | 27 +++++++-
 .../sp/oidc/profile/impl/BuildRequestObject.java   |  8 +++
 .../oidc/profile/impl/UiLocalesLookupStrategy.java | 52 +++++++++++++++
 .../profile/request/impl/AddUiLocalesHandler.java  | 67 +++++++++++++++++++
 .../profile/impl/UiLocalesLookupStrategyTest.java  | 77 ++++++++++++++++++++++
 7 files changed, 239 insertions(+), 1 deletion(-)

diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
index 033e0fb..167f8cc 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
@@ -48,6 +48,9 @@ public final class OIDCInitiatorConstants {
     
     /** Resource parameter */
     @Nonnull @NotEmpty public static final String RESOURCE = "resource";
+    
+    /** ui_locales parameter */
+    @Nonnull @NotEmpty public static final String UI_LOCALES = "ui_locales";
 
     /** authnContextClassRef input parameter. */
     @Nonnull @NotEmpty public static final String AUTHN_CONTEXT_CLASS_REF = AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME;
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
index 04fa62e..7a5bf47 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
@@ -181,6 +181,12 @@
                                 <bean class="net.shibboleth.sp.oidc.profile.impl.ResourceLookupStrategy" scope="prototype"/>
                             </property>
                         </bean>
+                        <bean id="AddUiLocales" scope="prototype"
+                            class="net.shibboleth.sp.oidc.profile.request.impl.AddUiLocalesHandler">
+                            <property name="parameterValueLookupStrategy">
+                                <bean class="net.shibboleth.sp.oidc.profile.impl.UiLocalesLookupStrategy" scope="prototype"/>
+                            </property>
+                        </bean>
                     </list>
                 </property>
             </bean>
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
index ad0a9a4..2974835 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
@@ -52,7 +52,8 @@ import net.shibboleth.sp.profile.SPConstants;
 import net.shibboleth.sp.profile.impl.IssueCorrelationCookie;
 
 /**
- * TODO these tests work using a signed request object atm, but the asserts need to work on url params as well
+ * TODO these tests work using a signed request object atm, we need to formalise that being set, and
+ * test using without the request object.
  */
 @ContextConfiguration(
         locations = {
@@ -255,6 +256,30 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
         assertValueForParameterFromRequestObject(req, "scope", "openid email profile");
     }
     
+    /**
+     * Basic flow test with ui_locales.
+     * 
+     * @throws Exception on error
+     */
+    @Test
+    public void testUiLocalesFromAgent() throws Exception {
+        setDefaultAuth();
+        
+        final DDF input = new DDF(null).structure();
+        input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();        
+        input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+        input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL); 
+        input.addmember(OIDCInitiatorConstants.UI_LOCALES).string("fr-CA fr en");
+        setApplicationRequest("test-oidc-application", input);
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        assertFlowExecutionOutcome(result.getOutcome());
+        
+        final AuthenticationRequest req = validateOutputMessage(result);
+        assertValueForParameterFromRequestObject(req, "ui_locales", "fr-CA fr en");
+    }
+    
     
     /**
      * Basic flow test with display.
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java
index 5fc353b..4d70d92 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java
@@ -18,6 +18,7 @@ import java.time.Duration;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -31,6 +32,7 @@ import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
 import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.langtag.LangTag;
 import com.nimbusds.oauth2.sdk.ResponseMode;
 import com.nimbusds.oauth2.sdk.id.Audience;
 import com.nimbusds.oauth2.sdk.id.Issuer;
@@ -266,6 +268,12 @@ public class BuildRequestObject extends AbstractProfileAction {
         setClaimIfPresent(requestObjectClaims, "login_hint", authnRequest.getLoginHint()); 
         setClaimIfPresent(requestObjectClaims, "prompt", authnRequest.getPrompt());
         setClaimIfPresent(requestObjectClaims, "display", authnRequest.getDisplay());
+
+        if (!authnRequest.getUiLocales().isEmpty()) {
+            final String locales = authnRequest.getUiLocales().stream().map(LangTag::toString).filter(s -> !s.isEmpty())
+                    .collect(Collectors.joining(" "));
+            setClaimIfPresent(requestObjectClaims, "ui_locales", locales);
+        }
         
         if (authnRequest.providerSupportsClaimsParameter()) {
             // Build the ACRs if set before adding the 'claims' claim
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategy.java
new file mode 100644
index 0000000..1bf60b9
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * 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.util.Arrays;
+import java.util.List;
+
+import org.opensaml.messaging.context.MessageContext;
+
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+
+/**
+ * A strategy function that extracts ui_locales from the inbound {@link MessageContext} and returns them as a List of 
+ * {@link String}s. Taking them from the inbound {@link DDF} object if they exist.
+ */
+public class UiLocalesLookupStrategy extends AbstractAgentAndRelyingPartyContextLookupFunction<List<String>>{
+
+    /** {@inheritDoc} */
+    @Override
+    public List<String> apply(final MessageContext messageContext) {
+        
+        final DDF input = getDDF(messageContext, DDFDirection.INPUT);
+        
+        List<String> uiLocales = CollectionSupport.emptyList(); 
+        
+        if (input != null) {
+            final String uiLocalesFromDDF = input.getmember(OIDCInitiatorConstants.UI_LOCALES).string();
+            if (StringSupport.trimOrNull(uiLocalesFromDDF) != null) {                
+                assert uiLocalesFromDDF != null;
+                uiLocales = Arrays.stream(uiLocalesFromDDF.split("\\s+")).filter(s -> !s.isEmpty())
+                        .toList();
+            }
+        }      
+        return uiLocales;
+    }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/request/impl/AddUiLocalesHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/request/impl/AddUiLocalesHandler.java
new file mode 100644
index 0000000..b804e99
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/request/impl/AddUiLocalesHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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.request.impl;
+
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.handler.MessageHandlerException;
+import org.slf4j.Logger;
+
+import com.nimbusds.langtag.LangTag;
+import com.nimbusds.langtag.LangTagException;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.oidc.profile.impl.AbstractAuthenticationRequestParameterValueMessageHandler;
+
+/**
+ * A message handler that sets the 'ui_locales' parameter on the authentication request.
+ */
+public class AddUiLocalesHandler extends AbstractAuthenticationRequestParameterValueMessageHandler<List<String>> {
+
+    /** Class logger. */
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(AddUiLocalesHandler.class);
+
+    /** Constructor.*/
+    public AddUiLocalesHandler() {
+        super((Class)List.class);
+    }
+
+    @Override
+    protected void doInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+        final List<String> locales = getParameterValue(messageContext);
+        if (locales != null && !locales.isEmpty()) {
+            if (log.isTraceEnabled()) {
+                log.trace("{} Setting 'ui_locales={}'", getLogPrefix(), locales);
+            }
+            final List<LangTag> uiLocals = locales.stream().map(tag -> {
+                try {
+                    return LangTag.parse(tag);
+                } catch (final LangTagException e) {
+                    log.warn("Can not parse language tag '{}'", tag);
+                }
+                return null;
+            }).filter(Objects::nonNull).toList();
+            
+            getAuthenticationRequest().setUiLocales(uiLocals);            
+        }
+    }
+
+}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategyTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategyTest.java
new file mode 100644
index 0000000..46c742e
--- /dev/null
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/UiLocalesLookupStrategyTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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 static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
+import net.shibboleth.sp.profile.InitiatorConstants;
+import net.shibboleth.sp.profile.SPConstants;
+
+/**
+ * Tests for {@link UiLocalesLookupStrategyTest}
+ */
+public class UiLocalesLookupStrategyTest extends AbstractAuthenticationLookupStrategyTest {
+    
+    /** The strategy to test.*/
+    private UiLocalesLookupStrategy strategy;
+
+    /** The DDF to store input parameters.*/
+    private DDF ddf;
+
+    @Override
+    @BeforeMethod
+    public void setUp() throws ComponentInitializationException {
+        super.setUp();
+        strategy = new UiLocalesLookupStrategy();
+
+        ddf = new DDF(null).structure();
+        ddf.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+        ddf.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
+        ddf.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+        addDDFToAgentRequestContext(ddf);
+    }
+
+    @Test
+    public void testThreeLocales() {
+        ddf.addmember(OIDCInitiatorConstants.UI_LOCALES).string("fr-CA fr en");
+        final List<String> result = strategy.apply(mc);
+        assertNotNull(result);
+        assert result != null;
+        assertEquals(result.size(), 3);
+        assertTrue(result.contains("fr-CA"));
+        assertTrue(result.contains("fr"));
+        assertTrue(result.contains("en"));
+    }
+    
+    @Test
+    public void testNoLocales() {        
+        final List<String> result = strategy.apply(mc);
+        assertNotNull(result);
+        assert result != null;
+        assertEquals(result.size(), 0);
+    }
+
+}

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


More information about the commits mailing list