[java-idp-plugin-duo] branch dev/JDUO-82 updated: Move admin beans into global context, and clean up enrollment condition.
Scott Cantor
cantor.2 at osu.edu
Mon Jan 8 16:46:34 UTC 2024
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch dev/JDUO-82
in repository java-idp-plugin-duo.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=ec564bd68dcd2c55f057ec878ee767f7c18b6ed1
The following commit(s) were added to refs/heads/dev/JDUO-82 by this push:
new ec564bd6 Move admin beans into global context, and clean up enrollment condition.
ec564bd6 is described below
commit ec564bd68dcd2c55f057ec878ee767f7c18b6ed1
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 8 11:46:31 2024 -0500
Move admin beans into global context, and clean up enrollment condition.
---
.../shibboleth/idp/authn/duo/DuoIntegration.java | 6 +++-
...n.java => PasswordlessEnrollmentCondition.java} | 35 ++++++++++++++-------
.../META-INF/net.shibboleth.idp/postconfig.xml | 25 +++++++++++++++
.../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml | 36 +++-------------------
4 files changed, 59 insertions(+), 43 deletions(-)
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java b/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
index 2248d862..da054dcc 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
@@ -15,13 +15,17 @@
package net.shibboleth.idp.authn.duo;
import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
- * Interface to a particular Duo integration point.
+ * Interface to a particular Duo AdminAPI or AuthAPI integration point.
+ *
+ * <p>This was superseded for "standard" authentication by the {@link DuoOIDCIntegration}
+ * interface but remains for other use cases.</p>
*
* @since 3.3.0
*/
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
similarity index 75%
rename from idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java
rename to idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
index 6071abf3..16f77b25 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrolmentCondition.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/PasswordlessEnrollmentCondition.java
@@ -18,6 +18,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -41,11 +42,11 @@ import net.shibboleth.shared.primitive.StringSupport;
* <p>The second parameter is the username to evaluate.</p>
*/
-public class PasswordlessEnrolmentCondition extends AbstractInitializableComponent
+public class PasswordlessEnrollmentCondition extends AbstractInitializableComponent
implements BiPredicate<ProfileRequestContext,String> {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(PasswordlessEnrolmentCondition.class);
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PasswordlessEnrollmentCondition.class);
/** The admin client used to access the DuoAdmin API.*/
@NonnullAfterInit private DuoAdminClient adminClient;
@@ -54,7 +55,7 @@ public class PasswordlessEnrolmentCondition extends AbstractInitializableCompone
@Nonnull @NonnullElements private Set<String> allowedLabels;
/** Constructor. */
- public PasswordlessEnrolmentCondition() {
+ public PasswordlessEnrollmentCondition() {
allowedLabels = CollectionSupport.emptySet();
}
@@ -103,23 +104,35 @@ public class PasswordlessEnrolmentCondition extends AbstractInitializableCompone
final Boolean enrolled = response.isEnrolled();
if (enrolled == null || !enrolled) {
- log.trace("User '{}' not enrolled", response.getUserId());
+ log.info("User '{}' not enrolled", response.getUsername());
return false;
}
final List<WebAuthnCredential> creds = response.getWebAuthnCredentials();
if (creds.isEmpty()) {
- log.trace("User '{}' has no WebAuthn credentials", response.getUserId());
+ log.info("User '{}' has no WebAuthn credentials", response.getUsername());
return false;
}
- log.trace("User '{}' is enrolled with {} WebAuthn credential(s)", response.getUserId(),
- response.getWebAuthnCredentials().size());
+ if (allowedLabels.isEmpty()) {
+ log.debug("User '{}' is enrolled with {} WebAuthn credential(s)", response.getUsername(),
+ response.getWebAuthnCredentials().size());
+ return true;
+ }
+
+ final Set<String> enrolledLabels = creds.stream()
+ .map(WebAuthnCredential::getLabel)
+ .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableSet())).get();
+ for (final String label : allowedLabels) {
+ if (enrolledLabels.contains(label)) {
+ log.debug("User '{}' has a qualifying WebAuthn credential: '{}'", response.getUsername(), label);
+ return true;
+ }
+ }
- final boolean allowed = creds.stream().map(WebAuthnCredential::getLabel).anyMatch(allowedLabels::contains);
- log.debug("User '{}' {} a qualifying WebAuthn credential", response.getUserId(),
- allowed ? "has" : "does not have");
- return allowed;
+ log.info("User '{}' has no acceptable WebAuthn credential, enrolled credentials: {}",
+ response.getUsername(), enrolledLabels);
+ return false;
} catch (final DuoException e) {
log.warn("Duo AdminAPI request failed, denying passwordless for '{}'", username, e);
diff --git a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 08bb6656..bed68aa2 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -52,5 +52,30 @@
<bean id="shibboleth.DuoOIDCAuthnController"
class="net.shibboleth.idp.plugin.authn.duo.impl.DuoOIDCAuthnController" />
+ <!-- Default Duo Admin API Integration for IdP-wide use. -->
+ <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" lazy-init="true"
+ class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
+ p:APIHost="%{idp.duo.oidc.admin.apiHost:%{idp.duo.oidc.apiHost:none}}"
+ p:integrationKey="%{idp.duo.oidc.admin.integrationKey:none}"
+ p:secretKey="%{idp.duo.oidc.admin.secretKey:none}"/>
+ <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant" lazy-init="true"
+ c:target-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" />
+
+ <bean id="shibboleth.authn.DuoOIDC.DefaultAdminClient" class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoAdminClient"
+ lazy-init="true"
+ p:objectMapper-ref="shibboleth.JSONObjectMapper"
+ p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.Admin.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+ p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}"
+ p:adminDuoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegrationStrategy"/>
+
+ <!-- Default passwordless condition that uses the admin API -->
+ <bean id="shibboleth.authn.DuoOIDC.Passwordless.DefaultCondition" lazy-init="true"
+ class="net.shibboleth.idp.plugin.authn.duo.PasswordlessEnrollmentCondition"
+ p:duoAdminClient="#{getObject('shibboleth.authn.DuoOIDC.AdminClient') ?: getObject('shibboleth.authn.DuoOIDC.DefaultAdminClient')}">
+ <property name="allowedLabels">
+ <bean parent="shibboleth.CommaDelimStringArray"
+ c:_0="#{'%{idp.duo.oidc.passwordless.allowedLabels:}'.trim()}" />
+ </property>
+ </bean>
</beans>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index c93bda6f..d5fa518a 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -80,15 +80,6 @@
<bean id="shibboleth.authn.DuoOIDC.Passwordless.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant"
c:target="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.DuoIntegration')}" />
- <!-- Default Duo Admin API Integration -->
- <bean id="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" lazy-init="false"
- class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
- p:APIHost="%{idp.duo.oidc.admin.apiHost:%{idp.duo.oidc.apiHost:none}}"
- p:integrationKey="%{idp.duo.oidc.admin.integrationKey:none}"
- p:secretKey="%{idp.duo.oidc.admin.secretKey:none}"/>
- <bean id="shibboleth.authn.DuoOIDC.Admin.DuoAdminIntegrationStrategy" parent="shibboleth.Functions.Constant"
- c:target-ref="shibboleth.authn.DuoOIDC.Admin.DuoIntegration" />
-
<!-- Default "optional" non-browser integration. -->
<bean id="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegration" lazy-init="false"
class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
@@ -158,34 +149,17 @@
<bean id="CheckPasswordlessEnrollment"
class="net.shibboleth.idp.plugin.authn.duo.impl.CheckPasswordlessEnrollment" scope="prototype"
p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
- p:passwordlessCondition="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.Condition') ?: getObject('DefaultPasswordlessCondition')}"
+ p:passwordlessCondition="#{getObject('shibboleth.authn.DuoOIDC.Passwordless.Condition') ?: getObject('shibboleth.authn.DuoOIDC.Passwordless.DefaultCondition')}"
p:dataSealer="#{'%{idp.authn.usernameCookieName:}'.trim().isEmpty() ? null : getObject('shibboleth.DataSealer')}"
p:cookieManager="#{'%{idp.authn.usernameCookieName:}'.trim().isEmpty() ? null : getObject('shibboleth.PersistentCookieManager')}"
p:cookieName="%{idp.authn.usernameCookieName:}"
- p:usernameFieldName="#{'%{idp.authn.oidc.usernameFieldName:j_username}'.trim()}"
+ p:usernameFieldName="#{'%{idp.duo.oidc.usernameFieldName:j_username}'.trim()}"
p:checkSession="%{idp.authn.usernameFromSession:false}"
- p:lowercase="%{idp.authn.oidc.lowercase:false}"
- p:uppercase="%{idp.authn.oidc.uppercase:false}"
- p:trim="%{idp.authn.oidc.trim:true}"
+ p:lowercase="%{idp.duo.oidc.lowercase:false}"
+ p:uppercase="%{idp.duo.oidc.uppercase:false}"
+ p:trim="%{idp.duo.oidc.trim:true}"
p:transforms="#{getObject('shibboleth.authn.DuoOIDC.Transforms')}" />
- <!-- Default passwordless condition that uses the admin API -->
- <bean id="DefaultPasswordlessCondition"
- class="net.shibboleth.idp.plugin.authn.duo.PasswordlessEnrolmentCondition"
- p:duoAdminClient="#{getObject('shibboleth.authn.DuoOIDC.AdminClient') ?: getObject('DefaultDuoAdminClient')}">
- <property name="allowedLabels">
- <bean parent="shibboleth.CommaDelimStringArray"
- c:_0="#{'%{idp.duo.oidc.passwordless.allowedLabels:}'.trim()}" />
- </property>
- </bean>
-
- <!-- Singleton, shared, Duo Admin API Beans -->
- <bean id="DefaultDuoAdminClient" class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoAdminClient"
- p:objectMapper-ref="shibboleth.JSONObjectMapper"
- p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.Admin.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
- p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}"
- p:adminDuoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.Admin.DuoAdminIntegrationStrategy"/>
-
<!-- Duo OIDC beans -->
<bean id="PopulateDuoAuthenticationContext" scope="prototype"
class="net.shibboleth.idp.plugin.authn.duo.impl.PopulateDuoAuthenticationContext"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list