[java-idp-plugin-oidc-rp] branch main updated: Add ACR and AMR response mappings. Add proxied authn time.
Phil Smart
philip.smart at jisc.ac.uk
Thu Sep 15 16:03:08 UTC 2022
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=b00649a193880235f0903e9fb7bfbd365aeb48d7
The following commit(s) were added to refs/heads/main by this push:
new b00649a Add ACR and AMR response mappings. Add proxied authn time.
b00649a is described below
commit b00649a193880235f0903e9fb7bfbd365aeb48d7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Sep 15 17:03:01 2022 +0100
Add ACR and AMR response mappings. Add proxied authn time.
---
...ticationContextClassRequestLookupFunction.java} | 6 +-
...ticationContextClassResponseLookupFunction.java | 115 +++++++++++++++++++
...thenticationContextClassLookupFunctionTest.java | 4 +-
...tionContextClassResponseLookupFunctionTest.java | 124 +++++++++++++++++++++
.../oidc/rp/impl/ValidateOIDCAuthentication.java | 86 +++++++++++---
.../idp/service/relying-party/postconfig.xml | 20 +++-
.../authn/oidc/rp/conf/authn/oidc-rp-config.xml | 14 ---
.../authn/oidc/rp/impl/AbstractOIDCTest.java | 5 +-
.../plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java | 89 ++++++++++++++-
.../plugin/authn/oidc/rp/impl/TestTokenHelper.java | 2 +
.../rp/impl/ValidateOIDCAuthenticationTest.java | 82 ++++++++++++--
11 files changed, 498 insertions(+), 49 deletions(-)
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
similarity index 95%
rename from idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java
rename to idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
index 0c53f8c..b7af435 100644
--- a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.java
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction.java
@@ -44,14 +44,14 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElemen
* 'passed through' or mapped by the given principal mappings. All input values are either SAML ACRs or AMRs,
* and all output values are OIDC ACRs.</p>
*/
-public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction
+public class ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction
implements Function<ProfileRequestContext,Collection<AuthenticationContextClassReferencePrincipal>> {
/** Mappings to transform proxied Principals. */
@Nonnull @NonnullElements private Map<Principal,Collection<Principal>> principalMappings;
/** Constructor. */
- public ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction() {
+ public ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction() {
principalMappings = Collections.emptyMap();
}
@@ -97,7 +97,7 @@ public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction
}
} else {
// If no mapping exists, just convert to correct output type - which might not make sense
- // to the downstream OP.
+ // to the upstream OP.
mappedAndPassedThroughPrincipals.add(
new AuthenticationContextClassReferencePrincipal(requestedPrincipal.getName()));
}
diff --git a/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java
new file mode 100644
index 0000000..7b6860d
--- /dev/null
+++ b/idp-oidc-rp-api/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction.java
@@ -0,0 +1,115 @@
+/*
+ * 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.authn.oidc.rp.config.navigate;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.utilities.java.support.annotation.ParameterName;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Implements a set of logic for determining the principals to use from OIDC 'amr' or 'acr' claims.
+ *
+ * <p>This operates for the OIDC to SAML proxy use case, in effect the reverse of the
+ * {@link ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction} function.
+ * All input values are either OIDC ACRs or AMRs, and all output values Java {@link Principal}s.
+ * The values are mapped by the given principal mappings. If a mapping does not exist, the AMR or ACR is ignored.
+ * </p>
+ */
+ at ThreadSafe
+public class ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction
+ implements Function<Collection<String>,Collection<Principal>> {
+
+ /** Mappings to transform proxied Principals. */
+ @Nonnull @NonnullElements private final Map<String,Collection<Principal>> principalMappings;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param mappings the AMR/ACR value to Principal mappings
+ */
+ public ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(
+ @Nullable @NonnullElements @ParameterName(name="mappings")
+ final Map<String,Collection<Principal>> mappings) {
+
+ if (mappings == null || mappings.isEmpty()) {
+ principalMappings = Collections.emptyMap();
+ } else {
+ principalMappings = new HashMap<>(mappings.size());
+ mappings.forEach((k, v) -> principalMappings.put(k, List.copyOf(v)));
+ }
+ }
+
+ @Override
+ public Collection<Principal> apply(final Collection<String> amrOrAcrs) {
+
+ if (amrOrAcrs != null) {
+ final List<Principal> principals = new ArrayList<>();
+ for (final String amrOrAcr : amrOrAcrs) {
+ if (principalMappings.containsKey(amrOrAcr)) {
+ final Collection<Principal> mappedPrincipals = principalMappings.get(amrOrAcr);
+ if (!mappedPrincipals.isEmpty()) {
+ principals.addAll(mappedPrincipals);
+ }
+ }
+ }
+ return principals;
+ }
+ return Collections.emptyList();
+ }
+
+ /** A simple lookup function that returns a singleton function.*/
+ public static class LookupFunctionWrapper
+ implements Function<ProfileRequestContext, Function<Collection<String>,Collection<Principal>>> {
+
+ /** A function used to map OIDC ACR/AMRs to Principals. A single instance is supplied.*/
+ private final Function<Collection<String>,Collection<Principal>> function;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param wrappedFunction the function to return when requested.
+ */
+ public LookupFunctionWrapper(@ParameterName(name="wrappedFunction")
+ final Function<Collection<String>,Collection<Principal>> wrappedFunction) {
+ function = Constraint.isNotNull(wrappedFunction, "Lookup function can not be null");
+ }
+
+ @Override
+ public Function<Collection<String>, Collection<Principal>> apply(final ProfileRequestContext prc) {
+ return function;
+ }
+
+ }
+}
+
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
index e4180a8..dced1f6 100644
--- a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest.java
@@ -20,7 +20,7 @@ import net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePr
/** Tests for ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.*/
public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest {
- private ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction function;
+ private ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction function;
private ProfileRequestContext prc;
@@ -32,7 +32,7 @@ public class ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunctionTest {
@BeforeMethod
public void setup() {
- function = new ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction();
+ function = new ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction();
prc = new ProfileRequestContext();
ac = prc.getSubcontext(AuthenticationContext.class, true);
nestedPrc = ac.getSubcontext(ProfileRequestContext.class, true);
diff --git a/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java
new file mode 100644
index 0000000..8e3cac7
--- /dev/null
+++ b/idp-oidc-rp-api/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/config/navigate/ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.authn.oidc.rp.config.navigate;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+
+/** Tests for ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction.*/
+public class ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunctionTest {
+
+ private ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction function;
+
+ private ProfileRequestContext prc;
+
+ private ProfileRequestContext nestedPrc;
+
+ private AuthenticationContext ac;
+
+ private RequestedPrincipalContext rpc;
+
+ @BeforeMethod
+ public void setup() {
+ prc = new ProfileRequestContext();
+ ac = prc.getSubcontext(AuthenticationContext.class, true);
+ nestedPrc = ac.getSubcontext(ProfileRequestContext.class, true);
+ rpc = ac.getSubcontext(RequestedPrincipalContext.class, true);
+ }
+
+ @Test
+ public void testSingleMappingSuccess() {
+
+ final Map<String,Collection<Principal>> mappings = new HashMap<>();
+ mappings.put(
+ "pwd",
+ List.of(new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+
+ function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);
+
+ final Collection<Principal> mapped = function.apply(List.of("pwd"));
+ assertTrue(mapped.contains(
+ new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+
+ }
+
+ @Test
+ public void testSingleMappingSuccess_NotMappedIgnored() {
+
+
+ final Map<String,Collection<Principal>> mappings = new HashMap<>();
+ mappings.put(
+ "pwd",
+ List.of(new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+ function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);;
+
+ // OTP should not be in the result
+ final Collection<Principal> mapped = function.apply(List.of("pwd","otp"));
+ assertEquals(mapped.size(), 1);
+ assertTrue(mapped.contains(
+ new AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")));
+
+ }
+
+ @Test
+ public void testSingleMappingSuccess_EmptyInput() {
+
+ // OTP should not be in the result
+ final Collection<Principal> mapped = function.apply(Collections.emptyList());
+ assertEquals(mapped.size(), 0);
+ }
+
+ @Test
+ public void testSingleMappingSuccess_NullMappings() {
+
+ function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(null);
+
+ // OTP should not be in the result
+ final Collection<Principal> mapped = function.apply(Collections.emptyList());
+ assertEquals(mapped.size(), 0);
+ }
+
+ @Test
+ public void testSingleMappingSuccess_EmptyMapping() {
+
+ final Map<String,Collection<Principal>> mappings = new HashMap<>();
+ mappings.put("pwd", Collections.emptyList());
+ function = new ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction(mappings);
+
+ // OTP should not be in the result
+ final Collection<Principal> mapped = function.apply(Collections.emptyList());
+ assertEquals(mapped.size(), 0);
+ }
+
+
+}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
index 18b2243..ea84f2a 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthentication.java
@@ -17,7 +17,11 @@
package net.shibboleth.idp.plugin.authn.oidc.rp.impl;
+import java.security.Principal;
+import java.text.ParseException;
import java.util.Collection;
+import java.util.Date;
+import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -60,7 +64,6 @@ import net.shibboleth.idp.authn.AuthnEventIds;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
import net.shibboleth.idp.authn.principal.ProxyAuthenticationPrincipal;
-import net.shibboleth.idp.authn.principal.UsernamePrincipal;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.principal.OIDCSubjectIdentifierPrincipal;
import net.shibboleth.idp.profile.IdPEventIds;
@@ -124,6 +127,12 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
/** Context for externally supplied inbound attributes. */
@Nullable private AttributeContext attributeContext;
+ /** Incoming context translation function for converting OIDC ACRs into Principals. */
+ @Nullable private Function<Collection<String>, Collection<Principal>> acrTranslator;
+
+ /** Incoming context translation function for converting OIDC ACRs into Principals. */
+ @Nullable private Function<Collection<String>, Collection<Principal>> amrTranslator;
+
/** Constructor.*/
public ValidateOIDCAuthentication() {
setMetricName(DEFAULT_METRIC_NAME);
@@ -213,7 +222,7 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
return false;
}
-
+
final RelyingPartyContext rpContext = relyingPartyContextLookupStrategy.apply(profileRequestContext);
if (rpContext == null) {
@@ -265,25 +274,68 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
}
//TODO use an attributeExtractionStrategy if registry not supplied, or in addition?
+ acrTranslator =
+ profileConfiguration.getAuthenticationContextClassReferenceTranslationStrategy(profileRequestContext);
+ amrTranslator =
+ profileConfiguration.getAuthenticationMethodsReferencesTranslationStrategy(profileRequestContext);
+
buildAuthenticationResult(profileRequestContext, authenticationContext);
-// if (authenticationContext.getAuthenticationResult() != null
-// && profileConfiguration.isProxiedAuthnInstant(profileRequestContext)) {
-// log.debug("{} Resetting authentication time to proxied value: {}", getLogPrefix(),
-// samlAuthnContext.getAuthnStatement().getAuthnInstant());
-// if (samlAuthnContext.getAuthnStatement().getAuthnInstant() != null) {
-// authenticationContext.getAuthenticationResult().setAuthenticationInstant(
-// samlAuthnContext.getAuthnStatement().getAuthnInstant());
-// }
-// }
+ if (authenticationContext.getAuthenticationResult() != null
+ && profileConfiguration.isProxiedAuthnInstant(profileRequestContext)) {
+
+ try {
+ if (endUserContext.getUnprocessedIdTokenClaims().getDateClaim("auth_time") != null) {
+ final Date authnTimeDate = endUserContext.getUnprocessedIdTokenClaims().getDateClaim("auth_time");
+ log.debug("{} Resetting authentication time to proxied value: {}", getLogPrefix(),
+ authnTimeDate);
+ authenticationContext.getAuthenticationResult().setAuthenticationInstant(authnTimeDate.toInstant());
+ } else {
+ log.debug("{} Unable to reset authentication time, auth_time not present in id_token",
+ getLogPrefix());
+ }
+ } catch (final ParseException e) {
+ log.debug("{} Unable to reset authentication time, auth_time could not be parsed from id_token: {}",
+ getLogPrefix(), e.getMessage());
+ }
+ }
}
@Override
protected Subject populateSubject(@Nonnull final Subject subject) {
+
+ if (acrTranslator != null && endUserContext.getUnprocessedIdTokenClaims().getClaim("acr") != null
+ && endUserContext.getUnprocessedIdTokenClaims().getClaim("acr") instanceof String) {
+ final String acr = (String)endUserContext.getUnprocessedIdTokenClaims().getClaim("acr");
+ final Collection<Principal> translated = acrTranslator.apply(List.of(acr));
+ if (translated != null && !translated.isEmpty()) {
+ subject.getPrincipals().addAll(translated);
+ if (log.isDebugEnabled()) {
+ log.debug("{} Added translated ACR Principals: {}", getLogPrefix(),
+ translated.stream().map(Principal::getName).collect(Collectors.toUnmodifiableList()));
+ }
+ }
+ }
- //Add ACR from OIDC request/response
+ if (amrTranslator != null && endUserContext.getUnprocessedIdTokenClaims().getClaim("amr") != null
+ && endUserContext.getUnprocessedIdTokenClaims().getClaim("amr") instanceof Collection) {
+ try {
+ final List<String> amrs = endUserContext.getUnprocessedIdTokenClaims().getStringListClaim("amr");
+ final Collection<Principal> translated = amrTranslator.apply(amrs);
+ if (translated != null && !translated.isEmpty()) {
+ subject.getPrincipals().addAll(translated);
+ if (log.isDebugEnabled()) {
+ log.debug("{} Added translated AMR Principals: {}", getLogPrefix(),
+ translated.stream().map(Principal::getName).collect(Collectors.toUnmodifiableList()));
+ }
+ }
+ } catch (final ParseException e) {
+ log.warn("Unable to parse AMR claims", e);
+ }
+
+ }
// TODO What type of subject? switch on subject_types_supported and subject_type.
if (endUserContext.getUnprocessedIdTokenClaims().getSubject() != null) {
@@ -305,18 +357,18 @@ public class ValidateOIDCAuthentication extends AbstractValidationAction {
}
/**
- * TODO FINISH Construct a populated {@link ProxyAuthenticationPrincipal} based on the inbound id_token
- * response.
+ * Construct a populated {@link ProxyAuthenticationPrincipal} based on the upstream OP (the
+ * issuer of the id_token) and the audiences of the token.
*
* @return a constructed {@link ProxyAuthenticationPrincipal} to include in the {@link Subject}
*/
@Nonnull private ProxyAuthenticationPrincipal buildProxyPrincipal() {
- final ProxyAuthenticationPrincipal proxied = new ProxyAuthenticationPrincipal();
-
+ // TODO: Is this useful in the OIDC context?
+ final ProxyAuthenticationPrincipal proxied = new ProxyAuthenticationPrincipal();
proxied.getAuthorities().add(endUserContext.getUnprocessedIdTokenClaims().getIssuer());
- // There is no proxy audience I know of in the OIDC or OAuth spec like there is in saml?
+ // TODO: There is no proxy audience I know of in the OIDC or OAuth spec like there is in saml?
return proxied;
}
diff --git a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 61e45a3..c33d401 100644
--- a/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-rp-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -36,9 +36,27 @@
</property>
<property name="defaultAuthenticationMethodsLookupStrategy">
<bean
- class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassLookupFunction"
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassRequestLookupFunction"
p:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyRequestMappings')}" />
</property>
+ <property name="authenticationContextClassReferenceTranslationStrategyLookupStrategy">
+ <bean
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction$LookupFunctionWrapper">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction"
+ c:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyResponseMappings')}"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="authenticationMethodsReferencesTranslationStrategyLookupStrategy">
+ <bean
+ class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction$LookupFunctionWrapper">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.authn.oidc.rp.config.navigate.ProxyAwareDefaultOIDCAuthenticationContextClassResponseLookupFunction"
+ c:mappings="#{getObject('shibboleth.authn.oidc.rp.PrincipalProxyResponseMappings')}"/>
+ </constructor-arg>
+ </bean>
+ </property>
</bean>
diff --git a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
index ed65094..6608675 100644
--- a/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
+++ b/idp-oidc-rp-impl/src/main/resources/net/shibboleth/idp/plugin/authn/oidc/rp/conf/authn/oidc-rp-config.xml
@@ -11,18 +11,4 @@
- <util:map id="shibboleth.authn.oidc.rp.PrincipalProxyRequestMappings">
- <entry>
- <key>
- <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="http://example.org/ac/classes/mfa" />
- </key>
- <list>
- <bean class="net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePrincipal"
- c:classRef="https://proxy.example.org/context2" />
- </list>
- </entry>
- </util:map>
-
-
-
</beans>
\ No newline at end of file
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
index ed3f9b6..80e68b1 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/AbstractOIDCTest.java
@@ -162,6 +162,9 @@ public abstract class AbstractOIDCTest {
/** The authentication context (above the nested prc).*/
protected AuthenticationContext ac;
+ /** The RP config.*/
+ protected OIDCAuthorizationConfiguration partyConfig;
+
/**
* Setup the various contexts.
*
@@ -202,7 +205,7 @@ public abstract class AbstractOIDCTest {
outMsgCtx.addSubcontext(metadataContext);
final RelyingPartyContext partyContext = new RelyingPartyContext();
- final OIDCAuthorizationConfiguration partyConfig = new OIDCAuthorizationConfiguration();
+ partyConfig = new OIDCAuthorizationConfiguration();
partyContext.setProfileConfig(partyConfig);
partyConfig.setClientCredential(TestCredentialHelper.createClientSecretCredential(CLIENT_SECRET));
partyConfig.setTokenEndpointAuthMethods(Set.of("client_secret_basic"));
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
index 41472d7..857679b 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/OIDCRPFlowTest.java
@@ -21,6 +21,8 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -79,7 +81,9 @@ import net.shibboleth.idp.plugin.authn.test.flow.AbstractAuthnXmlFlowExecutionTe
import net.shibboleth.idp.plugin.authn.test.flow.mock.MockFlowBuilder;
import net.shibboleth.idp.profile.context.RelyingPartyContext;
import net.shibboleth.idp.relyingparty.RelyingPartyConfiguration;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
+import net.shibboleth.oidc.authn.principal.AuthenticationContextClassReferencePrincipal;
import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
import net.shibboleth.oidc.profile.config.OIDCAuthorizationConfiguration;
import net.shibboleth.oidc.profile.config.OIDCSecurityConfiguration;
@@ -1101,8 +1105,61 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
assertFlowExecutionEnded();
assertNotNull(prc.getSubcontext(AuthenticationContext.class));
assertNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
-
-
+
+ }
+
+ @Test
+ public void testAuthnFlowFromAuthorizationCallback_Using_HMAC_UserInfo_And_IDToken_Response_WithACRAMRTranslation()
+ throws Exception {
+
+ setFlowPath(FLOW);
+ setFlowModelResources(flowResources);
+ setSubflows(subflows);
+
+ final Map<String,String> mockProperties = Map.of(
+ "idp.service.clientinfo.failFast","false",
+ "idp.entityID", "http://idp.example.com/");
+
+ setMockProperties(mockProperties);
+
+ final MockWebServer mockOPServer = createSimpleServer();
+ // First is token exchange
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/json")
+ .setBody(TestTokenHelper.createAccessTokenResponseJSONWithHMACIDToken(
+ OP_ISSUER_ID, CLIENT_ID, List.of(CLIENT_ID, "demo_rp2"), CLIENT_SECRET)));
+ // Second is userInfo
+ final var userInfoToken = TestTokenHelper.createHMACSignedUserInfoJWTResponseJSON(
+ OP_ISSUER_ID,CLIENT_ID, List.of(CLIENT_ID), CLIENT_SECRET);
+ mockOPServer.enqueue(new MockResponse().setResponseCode(200)
+ .setHeader("content-type", "application/jwt")
+ .setBody(userInfoToken.serialize()));
+ mockOPServer.start(9918);
+
+
+ final var flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+ .createFlowExecution(getFlowDefinition());
+ final ProfileRequestContext prc = populateBasicContextTreeFromAuthnResponse(flowExecution);
+ // Add mapping functions
+ addACRAndAMRFunctions(prc.getSubcontext(AuthenticationContext.class)
+ .getSubcontext(ProfileRequestContext.class));
+
+ updateFlowExecution(flowExecution);
+
+ //set start view and ending event to transition on.
+ externalContext.setEventId("proceed");
+ setCurrentState("AuthnRequest");
+ resumeFlow(externalContext);
+
+ mockOPServer.shutdown();
+
+ assertStandardSuccessConditions(prc);
+ // Add checks for added ACRs and AMRs
+ assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class).getSubject()
+ .getPrincipals(AuthenticationContextClassReferencePrincipal.class));
+ assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class).getSubject()
+ .getPrincipals(AuthenticationMethodPrincipal.class));
+
}
private ProfileRequestContext populateBasicContextTreeFromAuthnResponse(
@@ -1149,6 +1206,34 @@ public class OIDCRPFlowTest extends AbstractAuthnXmlFlowExecutionTests {
return prc;
}
+
+ /* Add functions to convert ACR and AMR in id_token responses to principals.*/
+ private void addACRAndAMRFunctions(@Nonnull final ProfileRequestContext prc) {
+ final OIDCAuthorizationConfiguration partyConfig =
+ (OIDCAuthorizationConfiguration) prc.getSubcontext(RelyingPartyContext.class).getProfileConfig();
+
+ partyConfig.setAuthenticationContextClassReferenceTranslationStrategyLookupStrategy(
+ context -> acrs -> {
+ final List<Principal> principals = new ArrayList<>();
+ for (final String acr : acrs) {
+ if ("urn:mace:incommon:iap:silver".equals(acr)){
+ principals.add(new AuthnContextClassRefPrincipal("urn:mace:incommon:iap:silver"));
+ }
+ }
+ return principals;
+ });
+ partyConfig.setAuthenticationMethodsReferencesTranslationStrategyLookupStrategy(
+ context -> amrs -> {
+ final List<Principal> principals = new ArrayList<>();
+ for (final String amr : amrs) {
+ if ("pwd".equals(amr)) {
+ principals.add(new
+ AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"));
+ }
+ }
+ return principals;
+ });
+ }
/**
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
index 445ff07..0ae02a7 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/TestTokenHelper.java
@@ -82,6 +82,8 @@ public final class TestTokenHelper {
.claim("nonce", nonce)
.claim("azp", clientId)
.claim("name",name)
+ .claim("acr","urn:mace:incommon:iap:silver")
+ .claim("amr", List.of("pwd", "otp"))
.claim("auth_time", new Date())
.issueTime(new Date())
.expirationTime(Date.from(Instant.now().plusSeconds(120)))
diff --git a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
index d2cdc8d..091d6e5 100644
--- a/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
+++ b/idp-oidc-rp-impl/src/test/java/net/shibboleth/idp/plugin/authn/oidc/rp/impl/ValidateOIDCAuthenticationTest.java
@@ -1,11 +1,30 @@
+/*
+ * 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.authn.oidc.rp.impl;
-import org.testng.annotations.Test;
-import org.testng.AssertJUnit;
+
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import java.security.Principal;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -37,11 +56,12 @@ import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
import net.shibboleth.idp.attribute.transcoding.impl.AttributeTranscoderRegistryImpl;
import net.shibboleth.idp.authn.context.AuthenticationContext;
import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
-import net.shibboleth.idp.authn.principal.UsernamePrincipal;
import net.shibboleth.idp.plugin.authn.oidc.rp.context.EndUserClaimsContext;
import net.shibboleth.idp.plugin.authn.oidc.rp.principal.OIDCSubjectIdentifierPrincipal;
import net.shibboleth.idp.plugin.authn.test.spring.MockApplicationContext;
import net.shibboleth.idp.profile.context.navigate.WebflowRequestContextProfileRequestContextLookup;
+import net.shibboleth.idp.saml.authn.principal.AuthenticationMethodPrincipal;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
import net.shibboleth.oidc.attribute.transcoding.OIDCAttributeTranscoder;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
@@ -87,7 +107,7 @@ public class ValidateOIDCAuthenticationTest extends AbstractOIDCTest {
registry.setApplicationContext(new MockApplicationContext());
registry.initialize();
- AssertJUnit.assertEquals(registry.getDisplayNames(new IdPAttribute("givenName")).size(), 1);
+ assertEquals(registry.getDisplayNames(new IdPAttribute("givenName")).size(), 1);
action.setTranscoderRegistry(new MockReloadableService<AttributeTranscoderRegistry>(registry));
}
@@ -133,6 +153,8 @@ public class ValidateOIDCAuthenticationTest extends AbstractOIDCTest {
.claim("nonce", "abadnonce")
.claim("azp", "https://rp.example.com")
.claim("name","jdoe")
+ .claim("acr","urn:mace:incommon:iap:silver")
+ .claim("amr", List.of("pwd", "otp"))
.expirationTime(Date.from(Instant.now().plusSeconds(120)))
.build());
@@ -149,20 +171,62 @@ public class ValidateOIDCAuthenticationTest extends AbstractOIDCTest {
setupAttributeFilter();
}
+
+ @Test
+ public void testSuccess_WithACRsAndAMRs() throws ComponentInitializationException {
+
+ partyConfig.setAuthenticationContextClassReferenceTranslationStrategyLookupStrategy(
+ context -> acrs -> {
+ final List<Principal> principals = new ArrayList<>();
+ for (final String acr : acrs) {
+ if ("urn:mace:incommon:iap:silver".equals(acr)){
+ principals.add(new AuthnContextClassRefPrincipal("urn:mace:incommon:iap:silver"));
+ }
+ }
+ return principals;
+ });
+ partyConfig.setAuthenticationMethodsReferencesTranslationStrategyLookupStrategy(
+ context -> amrs -> {
+ final List<Principal> principals = new ArrayList<>();
+ for (final String amr : amrs) {
+ if ("pwd".equals(amr)) {
+ principals.add(new
+ AuthenticationMethodPrincipal("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"));
+ }
+ }
+ return principals;
+ });
+
+ action.initialize();
+ final Event result = action.execute(src);
+
+ assertNull(result);
+ assertNotNull(ac.getAuthenticationResult());
+ assertNotNull(ac.getAuthenticationResult().getSubject());
+ final var subject = ac.getAuthenticationResult().getSubject();
+ assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
+ assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
+ assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
+ .iterator().next().getName(),"givenName");
+ assertEquals(subject.getPrincipals(AuthnContextClassRefPrincipal.class).size(), 1);
+ assertEquals(subject.getPrincipals(AuthenticationMethodPrincipal.class).size(), 1);
+
+ }
@Test
public void testSuccess() throws ComponentInitializationException {
action.initialize();
final Event result = action.execute(src);
- AssertJUnit.assertNull(result);
- AssertJUnit.assertNotNull(ac.getAuthenticationResult());
+ assertNull(result);
+ assertNotNull(ac.getAuthenticationResult());
assertNotNull(ac.getAuthenticationResult().getSubject());
final var subject = ac.getAuthenticationResult().getSubject();
- AssertJUnit.assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
- AssertJUnit.assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
- AssertJUnit.assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
+ assertEquals(subject.getPrincipals(OIDCSubjectIdentifierPrincipal.class).size(), 1);
+ assertEquals(subject.getPrincipals(IdPAttributePrincipal.class).size(), 1);
+ assertEquals(subject.getPrincipals(IdPAttributePrincipal.class)
.iterator().next().getName(),"givenName");
+
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list