[java-identity-provider] branch main updated: IDP-2068 - Remove legacy Duo flow

Scott Cantor cantor.2 at osu.edu
Tue Jul 18 16:34:16 UTC 2023


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

scantor pushed a commit to branch main
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=653f1212184bc67042cb0285e0f3da981fbaf44a

The following commit(s) were added to refs/heads/main by this push:
     new 653f12121 IDP-2068 - Remove legacy Duo flow
653f12121 is described below

commit 653f1212184bc67042cb0285e0f3da981fbaf44a
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Jul 18 12:33:50 2023 -0400

    IDP-2068 - Remove legacy Duo flow
    
    https://shibboleth.atlassian.net/browse/IDP-2068
    
    Complete removal of legacy code and dependencies.
---
 .../idp/authn/duo/BasicDuoIntegration.java         | 165 ----------
 .../net/shibboleth/idp/authn/duo/DuoAuthAPI.java   | 104 ------
 .../shibboleth/idp/authn/duo/DuoIntegration.java   |  61 ----
 .../net/shibboleth/idp/authn/duo/DuoPrincipal.java |  86 -----
 .../duo/context/DuoAuthenticationContext.java      | 179 ----------
 .../idp/authn/duo/context/package-info.java        |  24 --
 .../net/shibboleth/idp/authn/duo/package-info.java |  24 --
 idp-authn-impl/pom.xml                             |   5 -
 .../authn/duo/impl/AbstractDuoAuthenticator.java   | 161 ---------
 .../idp/authn/duo/impl/DuoAuthAPIResponse.java     |  62 ----
 .../idp/authn/duo/impl/DuoAuthAuthenticator.java   | 112 -------
 .../idp/authn/duo/impl/DuoAuthResponse.java        |  59 ----
 .../shibboleth/idp/authn/duo/impl/DuoDevice.java   | 107 ------
 .../idp/authn/duo/impl/DuoFailureResponse.java     |  79 -----
 .../authn/duo/impl/DuoPreauthAuthenticator.java    |  85 -----
 .../idp/authn/duo/impl/DuoPreauthResponse.java     |  61 ----
 .../idp/authn/duo/impl/DuoResponseWrapper.java     |  62 ----
 .../shibboleth/idp/authn/duo/impl/DuoSupport.java  | 218 ------------
 .../impl/ExtractDuoAuthenticationFromHeaders.java  | 263 ---------------
 .../idp/authn/duo/impl/ValidateDuoAuthAPI.java     | 366 ---------------------
 .../idp/authn/duo/impl/ValidateDuoWebResponse.java | 255 --------------
 .../idp/authn/duo/impl/package-info.java           |  24 --
 .../idp/authn/duo/impl/DuoSupportTest.java         |  33 --
 .../ExtractDuoAuthenticationFromHeadersTest.java   | 166 ----------
 .../net/shibboleth/idp/conf/authn-system.xml       |   9 -
 25 files changed, 2770 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/BasicDuoIntegration.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/BasicDuoIntegration.java
deleted file mode 100644
index 5d2fdc1f3..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/BasicDuoIntegration.java
+++ /dev/null
@@ -1,165 +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.authn.duo;
-
-import java.security.Principal;
-import java.util.Collection;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.security.auth.Subject;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.component.AbstractInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.StringSupport;
-
-/**
- * Wrapper for use of Duo.
- * 
- * @since 3.3.0
- */
-public class BasicDuoIntegration extends AbstractInitializableComponent implements DuoIntegration {
-
-    /** API host. */
-    @NonnullAfterInit @NotEmpty private String apiHost;
-    
-    /** Application key. */
-    @Nullable @NotEmpty private String applicationKey;
-    
-    /** Integration key. */
-    @NonnullAfterInit @NotEmpty private String integrationKey;
-    
-    /** Secret key. */
-    @NonnullAfterInit @NotEmpty private String secretKey;
-    
-    /** Container for supported principals. */
-    @Nonnull private final Subject supportedPrincipals;
-    
-    /** Constructor. */
-    public BasicDuoIntegration() {
-        supportedPrincipals = new Subject();
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull @NotEmpty public String getAPIHost() {
-        checkComponentActive();
-        assert apiHost != null;
-        return apiHost;
-    }
-    
-    /**
-     * Set the API host to use.
-     * 
-     * @param host API host
-     */
-    public void setAPIHost(@Nonnull @NotEmpty final String host) {
-        checkSetterPreconditions();
-        apiHost = Constraint.isNotNull(StringSupport.trimOrNull(host), "API host cannot be null or empty");
-    }
-
-    /** {@inheritDoc} */
-    @Nullable @NotEmpty public String getApplicationKey() {
-        return applicationKey;
-    }
-    
-    /**
-     * Set the application key to use.
-     * 
-     * @param key application key
-     */
-    public void setApplicationKey(@Nullable @NotEmpty final String key) {
-        checkSetterPreconditions();
-        applicationKey = StringSupport.trimOrNull(key);
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull @NotEmpty public String getIntegrationKey() {
-        checkComponentActive();
-        assert integrationKey != null;
-        return integrationKey;
-    }
-    
-    /**
-     * Set the integration key to use.
-     * 
-     * @param key integration key
-     */
-    public void setIntegrationKey(@Nonnull @NotEmpty final String key) {
-        checkSetterPreconditions();
-        integrationKey = Constraint.isNotNull(StringSupport.trimOrNull(key), "Integration key cannot be null or empty");
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull @NotEmpty public String getSecretKey() {
-        checkComponentActive();
-        assert secretKey != null;
-        return secretKey;
-    }
-    
-    /**
-     * Set the secret key to use.
-     * 
-     * @param key secret key
-     */
-    public void setSecretKey(@Nonnull @NotEmpty final String key) {
-        checkSetterPreconditions();
-        secretKey = Constraint.isNotNull(StringSupport.trimOrNull(key), "Secret key cannot be null or empty");
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull @Unmodifiable @NotLive public <T extends Principal> Set<T> getSupportedPrincipals(
-            @Nonnull final Class<T> c) {
-        final Set<T> result = supportedPrincipals.getPrincipals(c);
-        assert result != null;
-        return result;
-    }
-    
-    /**
-     * Set supported non-user-specific principals that the action will include in the subjects
-     * it generates, in place of any default principals from the flow.
-     * 
-     * <p>Setting to a null or empty collection will maintain the default behavior of relying on the flow.</p>
-     * 
-     * @param <T> a type of principal to add, if not generic
-     * @param principals supported principals to include
-     */
-    public <T extends Principal> void setSupportedPrincipals(@Nullable final Collection<T> principals) {
-        checkSetterPreconditions();
-
-        supportedPrincipals.getPrincipals().clear();
-        
-        if (principals != null && !principals.isEmpty()) {
-            supportedPrincipals.getPrincipals().addAll(Set.copyOf(principals));
-        }
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        if (apiHost == null || integrationKey == null || secretKey == null) {
-            throw new ComponentInitializationException("API host and integration keys must be set");
-        }
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoAuthAPI.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoAuthAPI.java
deleted file mode 100644
index 07055ff60..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoAuthAPI.java
+++ /dev/null
@@ -1,104 +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.authn.duo;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-
-/**
- * Constants defined in the Duo AuthAPI.
- * 
- * @since 3.4.0
- */
-public final class DuoAuthAPI {
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_USERNAME = "username";
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_IPADDR = "ipaddr";
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR = "factor";
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_DEVICE = "device";
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_PASSCODE = "passcode";
-
-    /** Duo AuthAPI parameter name. */
-    @Nonnull @NotEmpty public static final String DUO_PUSHINFO = "pushinfo";
-
-    /** Duo AuthAPI factor "auto" value. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_AUTO = "auto";
-
-    /** Duo AuthAPI factor "push" value. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_PUSH = "push";
-
-    /** Duo AuthAPI factor "passcode" value. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_PASSCODE = "passcode";
-
-    /** Duo AuthAPI factor "sms" value. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_SMS = "sms";
-
-    /** Duo AuthAPI factor "enum" value. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_PHONE = "phone";
-
-    /** Duo AuthAPI device "auto" value. */
-    @Nonnull @NotEmpty public static final String DUO_DEVICE_AUTO = "auto";
-
-    /** Duo AuthAPI preauth "allow" result value. */
-    @Nonnull @NotEmpty public static final String DUO_PREAUTH_RESULT_ALLOW = "allow";
-
-    /** Duo AuthAPI preauth "auth" result value. */
-    @Nonnull @NotEmpty public static final String DUO_PREAUTH_RESULT_AUTH = "auth";
-
-    /** Duo AuthAPI preauth "deny" result value. */
-    @Nonnull @NotEmpty public static final String DUO_PREAUTH_RESULT_DENY = "deny";
-
-    /** Duo AuthAPI preauth "enroll" result value. */
-    @Nonnull @NotEmpty public static final String DUO_PREAUTH_RESULT_ENROLL = "enroll";
-
-    /** Duo AuthAPI auth "allow" result value. */
-    @Nonnull @NotEmpty public static final String DUO_AUTH_RESULT_ALLOW = "allow";
-
-    /** Duo AuthAPI auth "deny" result value. */
-    @Nonnull @NotEmpty public static final String DUO_AUTH_RESULT_DENY = "deny";
-
-    /** Duo AuthAPI auth "bypass" result value. */
-    @Nonnull @NotEmpty public static final String DUO_AUTH_STATUS_BYPASS = "bypass";
-
-    /** Duo AuthAPI auth "locked_out" result value. */
-    @Nonnull @NotEmpty public static final String DUO_AUTH_STATUS_LOCKED = "locked_out";
-
-    /** Duo flow default header name for factor. */
-    @Nonnull @NotEmpty public static final String DUO_FACTOR_HEADER_NAME = "X-Shibboleth-Duo-Factor";
-    
-    /** Duo flow default header name for device ID. */
-    @Nonnull @NotEmpty public static final String DUO_DEVICE_HEADER_NAME = "X-Shibboleth-Duo-Device";
-
-    /** Duo flow default header name for passcode. */
-    @Nonnull @NotEmpty public static final String DUO_PASSCODE_HEADER_NAME = "X-Shibboleth-Duo-Passcode";
-
-    /** Constructor. */
-    private DuoAuthAPI() {
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
deleted file mode 100644
index fd05dca07..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoIntegration.java
+++ /dev/null
@@ -1,61 +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.authn.duo;
-
-import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-/**
- * Interface to a particular Duo integration point.
- * 
- * @since 3.3.0
- */
-public interface DuoIntegration extends PrincipalSupportingComponent {
-    
-    /**
-     * Get the name of the API host to contact.
-     * 
-     * @return name of API host
-     */
-    @Nonnull @NotEmpty String getAPIHost();
-
-    /**
-     * Get the application key.
-     * 
-     * @return the application key
-     */
-    @Nullable @NotEmpty String getApplicationKey();
-
-    /**
-     * Get the integration key.
-     * 
-     * @return the integration key
-     */
-    @Nonnull @NotEmpty String getIntegrationKey();
-
-    /**
-     * Get the secret key.
-     * 
-     * @return the secret key
-     */
-    @Nonnull @NotEmpty String getSecretKey();
-    
-}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoPrincipal.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoPrincipal.java
deleted file mode 100644
index a4ff6a0c8..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/DuoPrincipal.java
+++ /dev/null
@@ -1,86 +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.authn.duo;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.principal.CloneablePrincipal;
-import net.shibboleth.shared.annotation.ParameterName;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.StringSupport;
-
-import com.google.common.base.MoreObjects;
-
-/** Principal based on a Duo authentication. */
-public class DuoPrincipal implements CloneablePrincipal {
-
-    /** The username. */
-    @Nonnull @NotEmpty private String username;
-
-    /**
-     * Constructor.
-     * 
-     * @param name the username
-     */
-    public DuoPrincipal(@Nonnull @NotEmpty @ParameterName(name="name") final String name) {
-        username = Constraint.isNotNull(StringSupport.trimOrNull(name), "Username cannot be null or empty");
-    }
-
-    /** {@inheritDoc} */
-    @Nonnull @NotEmpty public String getName() {
-        return username;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int hashCode() {
-        return username.hashCode();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean equals(final Object other) {
-        if (other == null) {
-            return false;
-        }
-
-        if (this == other) {
-            return true;
-        }
-
-        if (other instanceof DuoPrincipal) {
-            return username.equals(((DuoPrincipal) other).getName());
-        }
-
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(this).add("username", username).toString();
-    }
-    
-    /** {@inheritDoc} */
-    @Nonnull public DuoPrincipal clone() throws CloneNotSupportedException {
-        final DuoPrincipal copy = (DuoPrincipal) super.clone();
-        copy.username = username;
-        return copy;
-    }
-}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java
deleted file mode 100644
index a43d2cb16..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/DuoAuthenticationContext.java
+++ /dev/null
@@ -1,179 +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.authn.duo.context;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.shared.annotation.constraint.Live;
-
-/**
- * Context that carries Duo factor and device or passcode to be used in validation.
- * 
- * <p>This is used for AuthAPI-based use of Duo rather than the usual delegation
- * of the process to their Web SDK.</p>
- * 
- * @parent {@link AuthenticationContext}
- * @added After extracting the Duo factor and device or passcode during authentication
- */
-public final class DuoAuthenticationContext extends BaseContext {
-
-    /** Username. */
-    @Nullable private String username;
-    
-    /** Client address. */
-    @Nullable private String clientAddress;
-    
-    /** Factor. */
-    @Nullable private String duoFactor;
-
-    /** Device ID. */
-    @Nullable private String duoDevice;
-
-    /** Passcode. */
-    @Nullable private String duoPasscode;
-    
-    /** PushInfo data. */
-    @Nonnull private Map<String,String> pushInfo;
-
-    /** Constructor. */
-    public DuoAuthenticationContext() {
-        pushInfo = new HashMap<>();
-    }
-    
-    /**
-     * Get the username.
-     * 
-     * @return username
-     */
-    @Nullable public String getUsername() {
-        return username;
-    }
-    
-    /**
-     * Set the username.
-     * 
-     * @param name username
-     * 
-     * @return this context
-     */
-    @Nonnull public DuoAuthenticationContext setUsername(@Nullable final String name) {
-        username = name;
-        return this;
-    }
-
-    /**
-     * Get the client address.
-     * 
-     * @return address
-     */
-    @Nullable public String getClientAddress() {
-        return clientAddress;
-    }
-    
-    /**
-     * Set the client address.
-     * 
-     * @param address client address
-     * 
-     * @return this context
-     */
-    @Nonnull public DuoAuthenticationContext setClientAddress(@Nullable final String address) {
-        clientAddress = address;
-        return this;
-    }
-
-    /**
-     * Get the device ID.
-     * 
-     * @return the Duo device identifier
-     */
-    @Nullable public String getDeviceID() {
-        return duoDevice;
-    }
-
-    /**
-     * Set the device ID.
-     * 
-     * @param deviceId the Duo device identifier
-     * 
-     * @return this context
-     */
-    @Nonnull public DuoAuthenticationContext setDeviceID(@Nullable final String deviceId) {
-        duoDevice = deviceId;
-        return this;
-    }
-
-    /**
-     * Get the factor to use.
-     * 
-     * @return the factor to use
-     */
-    @Nullable public String getFactor() {
-        return duoFactor;
-    }
-
-    /**
-     * Set the factor to use.
-     * 
-     * @param factor the Duo factor
-     * 
-     * @return this context
-     */
-    @Nonnull public DuoAuthenticationContext setFactor(@Nullable final String factor) {
-        duoFactor = factor;
-        return this;
-    }
-
-    /**
-     * Get the passcode.
-     * 
-     * @return the passcode
-     */
-    @Nullable public String getPasscode() {
-        return duoPasscode;
-    }
-
-    /**
-     * Set the passcode.
-     * 
-     * @param passcode the passcode
-     * 
-     * @return this context
-     */
-    @Nonnull public DuoAuthenticationContext setPasscode(@Nullable final String passcode) {
-        duoPasscode = passcode;
-        return this;
-    }
-
-    /**
-     * Get the pushinfo.
-     * 
-     * @return the pushinfo
-     */
-    @Nonnull @Live public Map<String,String> getPushInfo() {
-        return pushInfo;
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/package-info.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/package-info.java
deleted file mode 100644
index 7c10173ca..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/context/package-info.java
+++ /dev/null
@@ -1,24 +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.
- */
-
-/**
- * Context classes related to Duo authentication.
- */
- at NonnullElements
-package net.shibboleth.idp.authn.duo.context;
-
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/package-info.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/package-info.java
deleted file mode 100644
index 1e08c7224..000000000
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/duo/package-info.java
+++ /dev/null
@@ -1,24 +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.
- */
-
-/**
- * Public APIs related to Duo authentication.
- */
- at NonnullElements
-package net.shibboleth.idp.authn.duo;
-
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/idp-authn-impl/pom.xml b/idp-authn-impl/pom.xml
index 4f4a9220b..a451db4ee 100644
--- a/idp-authn-impl/pom.xml
+++ b/idp-authn-impl/pom.xml
@@ -185,11 +185,6 @@
             <artifactId>ldaptive</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>com.duosecurity</groupId>
-            <artifactId>DuoWeb</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/AbstractDuoAuthenticator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/AbstractDuoAuthenticator.java
deleted file mode 100644
index b64af49f8..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/AbstractDuoAuthenticator.java
+++ /dev/null
@@ -1,161 +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.authn.duo.impl;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.apache.hc.client5.http.classic.HttpClient;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.opensaml.security.httpclient.HttpClientSecurityParameters;
-import org.opensaml.security.httpclient.HttpClientSecuritySupport;
-
-import com.duosecurity.duoweb.DuoWebException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.AbstractInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-
-/**
- * A base class for authentication actions which call a Duo AuthAPI endpont.
- */
- at ThreadSafe
-public abstract class AbstractDuoAuthenticator extends AbstractInitializableComponent {
-
-    /** HttpClient for contacting Duo. */
-    @NonnullAfterInit private HttpClient httpClient;
-
-    /** HTTP client security parameters. */
-    @Nullable private HttpClientSecurityParameters httpClientSecurityParameters;
-    
-    /** JSON object mapper. */
-    @NonnullAfterInit private ObjectMapper objectMapper;
-    
-    /**
-     * Set the {@link HttpClient} to use for contacting Duo.
-     * 
-     * @param client HttpClient
-     */
-    public void setHttpClient(@Nonnull final HttpClient client) {
-        checkSetterPreconditions();
-        httpClient = Constraint.isNotNull(client, "HTTP client cannot be null");
-    }
-
-    /**
-     * Set the optional client security parameters.
-     * 
-     * @param params the new client security parameters
-     */
-    public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
-        checkSetterPreconditions();
-        httpClientSecurityParameters = params;
-    }
-    
-    /**
-     * Set the JSON {@link ObjectMapper}.
-     * 
-     * @param mapper object mapper
-     */
-    public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
-        checkSetterPreconditions();
-        objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        
-        if (httpClient == null) {
-            throw new ComponentInitializationException("HttpClient cannot be null");
-        }
-
-        if (objectMapper == null) {
-            throw new ComponentInitializationException("ObjectMapper cannot be null");
-        }
-    }
-
-    /**
-     * Performs a call to the Duo AuthAPI. Upon a successful call, the JSON response is mapped into the appropriate type
-     * of {@link DuoResponseWrapper}.
-     * 
-     * @param request the prepared HTTP request
-     * @param wrapperTypeRef the type of {@link DuoResponseWrapper} to use
-     * @param <T> the DuoResponse type being wrapped
-     * 
-     * @return a {@link DuoResponseWrapper}
-     * 
-     * @throws IOException on an I/O error
-     * @throws DuoWebException on a Duo-related error
-     */
-    protected <T extends DuoResponseWrapper<?>> T doAPIRequest(@Nonnull final ClassicHttpRequest request,
-            @Nonnull final TypeReference<T> wrapperTypeRef)
-                    throws DuoWebException, IOException {
-
-        // Make the request.
-        final HttpClientContext clientContext = HttpClientContext.create();
-        assert clientContext != null;
-        HttpClientSecuritySupport.marshalSecurityParameters(clientContext, httpClientSecurityParameters, true);
-        HttpClientSecuritySupport.addDefaultTLSTrustEngineCriteria(clientContext, request);
-        final ClassicHttpResponse httpResponse = httpClient.executeOpen(null, request, clientContext);
-        final String scheme = request.getScheme();
-        assert scheme != null;
-        HttpClientSecuritySupport.checkTLSCredentialEvaluated(clientContext, scheme);
-
-        // Check the HTTP response code.
-        final int httpStatusCode = httpResponse.getCode();
-        if (httpStatusCode == HttpStatus.SC_BAD_REQUEST) {
-            final InputStream httpContent = httpResponse.getEntity().getContent();
-            final DuoFailureResponse msg = objectMapper.readValue(httpContent, DuoFailureResponse.class);
-            final StringBuilder builder = new StringBuilder();
-            builder.append(msg.getMessage() != null ? msg.getMessage() : "no message")
-                .append(" (")
-                .append(msg.getMessageDetail() != null ? msg.getMessageDetail() : "no detail")
-                .append(")");
-            throw new DuoWebException(builder.toString());
-        }
-        if (httpStatusCode != HttpStatus.SC_OK) {
-            throw new IOException("Non-ok status code (" + httpStatusCode + ") returned from Duo: "
-                    + httpResponse.getReasonPhrase());
-        } else if (httpResponse.getEntity() == null) {
-            throw new IOException("No response body returned from Duo");
-        }
-
-        // Parse the JSON response.
-        final T duoResponse = objectMapper.readValue(httpResponse.getEntity().getContent(), wrapperTypeRef);
-
-        if (duoResponse == null) {
-            throw new DuoWebException("Unable to parse JSON response");
-        } else if (!"OK".equals(duoResponse.getStat())) {
-            throw new DuoWebException("Unexpected status value in JSON response: " + duoResponse.getStat());
-        }
-
-        return duoResponse;
-    }
-
-}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAPIResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAPIResponse.java
deleted file mode 100644
index 123cac4df..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAPIResponse.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.authn.duo.impl;
-
-import javax.annotation.Nonnull;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Describes the results of a Duo AuthAPI call, intended for use with a jackson
- * {@link com.fasterxml.jackson.databind.ObjectMapper}.
- * 
- * @since 3.4.0
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public abstract class DuoAuthAPIResponse {
-
-    /** the result. */
-    @JsonProperty("result")
-    private String result;
-    
-    /** the status message. */
-    @JsonProperty("status_msg")
-    private String statusMessage;
-    
-    /**
-     * Get the Duo result string.
-     * 
-     * @return the result string
-     */
-    @Nonnull public String getResult() {
-        assert result != null;
-        return result;
-    }
-    
-    /**
-     * Get the Duo status message.
-     * 
-     * @return the Duo status message
-     */
-    @Nonnull public String getStatusMessage() {
-        assert statusMessage  != null;
-        return statusMessage;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAuthenticator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAuthenticator.java
deleted file mode 100644
index 039492886..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthAuthenticator.java
+++ /dev/null
@@ -1,112 +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.authn.duo.impl;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
-import org.apache.hc.core5.net.URIBuilder;
-
-import com.duosecurity.duoweb.DuoWebException;
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-import net.shibboleth.idp.authn.duo.DuoIntegration;
-import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
-import net.shibboleth.shared.primitive.StringSupport;
-
-/**
- * Implementation of the the Duo AuthApi /v2/auth endpoint.
- */
-public class DuoAuthAuthenticator extends AbstractDuoAuthenticator {
-    
-    /** pushinfo escaper. */
-    @Nonnull private final Escaper paramEscaper;
-
-    /** a TypeReference for the repsonse generated by the endpoint. */
-    @Nonnull private final TypeReference<DuoResponseWrapper<DuoAuthResponse>> wrapperTypeRef;
-    
-    /** Constructor. */
-    @SuppressWarnings("null")
-    public DuoAuthAuthenticator() {
-        wrapperTypeRef = new TypeReference<>() {};
-        paramEscaper = UrlEscapers.urlFormParameterEscaper();
-    }
-    
-    /**
-     * Perform an authentication action via the Duo AuthApi /auth endpoint.
-     * 
-     * @param duoContext Duo authentication context to use
-     * @param duoIntegration Duo integration to use
-     * 
-     * @return a {@link DuoAuthResponse}
-     * 
-     * @throws DuoWebException if an error occurs
-     */
-    public DuoAuthResponse authenticate(@Nonnull final DuoAuthenticationContext duoContext,
-            @Nonnull final DuoIntegration duoIntegration) throws DuoWebException {
-
-        try {
-            // prepare the request
-            final URI uri = new URIBuilder().setScheme("https").setHost(duoIntegration.getAPIHost())
-                    .setPath("/auth/v2/auth").build();
-            final ClassicRequestBuilder rb =
-                    ClassicRequestBuilder.post().setUri(uri).addParameter(DuoAuthAPI.DUO_USERNAME, duoContext.getUsername());
-            assert rb != null;
-            if (duoContext.getClientAddress() != null) {
-                rb.addParameter(DuoAuthAPI.DUO_IPADDR, duoContext.getClientAddress());
-            }
-            if (duoContext.getFactor() != null) {
-                rb.addParameter(DuoAuthAPI.DUO_FACTOR, duoContext.getFactor());
-            }
-            if (duoContext.getDeviceID() != null) {
-                rb.addParameter(DuoAuthAPI.DUO_DEVICE, duoContext.getDeviceID());
-            }
-            if (duoContext.getPasscode() != null) {
-                rb.addParameter(DuoAuthAPI.DUO_PASSCODE, duoContext.getPasscode());
-            }
-            if (!duoContext.getPushInfo().isEmpty()) {
-                final ArrayList<String> pushinfo = new ArrayList<>(duoContext.getPushInfo().size());
-                for (final Map.Entry<String,String> entry : duoContext.getPushInfo().entrySet()) {
-                    pushinfo.add(paramEscaper.escape(entry.getKey()) + "=" + paramEscaper.escape(entry.getValue()));
-                }
-                rb.addParameter(DuoAuthAPI.DUO_PUSHINFO, StringSupport.listToStringValue(pushinfo, "&"));
-            }
-            DuoSupport.signRequest(rb, duoIntegration);
-            final ClassicHttpRequest request = rb.build();
-            assert request != null;
-
-            // do it
-            return doAPIRequest(request, wrapperTypeRef).getResponse();
-        } catch (final IOException | URISyntaxException | InvalidKeyException | NoSuchAlgorithmException ex) {
-            throw new DuoWebException("Duo AuthAPI auth request failed: " + ex.getMessage());
-        }
-    }
-
-}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthResponse.java
deleted file mode 100644
index a94062fa2..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoAuthResponse.java
+++ /dev/null
@@ -1,59 +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.authn.duo.impl;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Describes the results of an authentication attempt via the Duo AuthAPI, intended for use with a jackson
- * {@link com.fasterxml.jackson.databind.ObjectMapper}.
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public class DuoAuthResponse extends DuoAuthAPIResponse {
-
-    /** the status string. */
-    @JsonProperty("status") private String status;
-
-    /** the trusted device token string. */
-    @JsonProperty("trusted_device_token") private String trustedDeviceToken;
-
-    /**
-     * Get the Duo status string.
-     * 
-     * @return Duo status string
-     */
-    @Nonnull public String getStatus() {
-        assert status != null;
-        return status;
-    }
-
-    /**
-     * Get the Duo trusted device token string.
-     * 
-     * @return Duo trusted device token string
-     */
-    @Nullable public String getTrustedDeviceToken() {
-        assert trustedDeviceToken != null;
-        return trustedDeviceToken;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoDevice.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoDevice.java
deleted file mode 100644
index ca6181a5f..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoDevice.java
+++ /dev/null
@@ -1,107 +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.authn.duo.impl;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Represents a Duo device, intended for use with a jackson
- * {@link com.fasterxml.jackson.databind.ObjectMapper}.
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public class DuoDevice {
-
-    /** the Duo device identifier. */
-    @JsonProperty("device") @Nullable private String device;
-
-    /** the Duo device type. */
-    @JsonProperty("type") @Nullable private String type;
-
-    /** the Duo device number. */
-    @JsonProperty("number") @Nullable private String number;
-
-    /** the Duo device name. */
-    @JsonProperty("name") @Nullable private String name;
-
-    /** a {@link List} of Duo device capabilities. */
-    @JsonProperty("capabilities") @Nullable private List<String> capabilities;
-
-    /** Constructor. */
-    public DuoDevice() {
-        capabilities = new ArrayList<>();
-    }
-
-    /**
-     * Get the device identifier.
-     * 
-     * @return the device identifier
-     */
-    @Nonnull public String getDevice() {
-        assert device != null;
-        return device;
-    }
-
-    /**
-     * Get the device type.
-     * 
-     * @return the device type
-     */
-    @Nonnull public String getType() {
-        assert type != null;
-        return type;
-    }
-
-    /**
-     * Get the device number.
-     * 
-     * @return the device number
-     */
-    @Nonnull public String getNumber() {
-        assert number != null;
-        return number;
-    }
-
-    /**
-     * Get the device name.
-     * 
-     * @return the device name
-     */
-    @Nonnull public String getName() {
-        assert name != null;
-        return name;
-    }
-
-    /**
-     * Get the device capabilities.
-     * 
-     * @return the device capabilities
-     */
-    @Nonnull public Collection<String> getCapabilities() {
-        assert capabilities != null;
-        return capabilities;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoFailureResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoFailureResponse.java
deleted file mode 100644
index 5dd7d3545..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoFailureResponse.java
+++ /dev/null
@@ -1,79 +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.authn.duo.impl;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import javax.annotation.Nullable;
-
-/**
- * Describes the failure of a Duo AuthAPI call.
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public class DuoFailureResponse {
-
-    /** the failure status. */
-    @JsonProperty("stat") @Nullable private String stat;
-
-    /** the failure code. */
-    @JsonProperty("code") @Nullable private String code;
-
-    /** the failure message. */
-    @JsonProperty("message") @Nullable private String message;
-
-    /** the failure message detail. */
-    @JsonProperty("message_detail") @Nullable private String messageDetail;
-
-    /**
-     * Get the failure status.
-     * 
-     * @return failure status
-     */
-    @Nullable public String getStat() {
-        return stat;
-    }
-
-    /**
-     * Get the failure code.
-     * 
-     * @return failure code
-     */
-    @Nullable public String getCode() {
-        return code;
-    }
-
-    /**
-     * Get the failure message.
-     * 
-     * @return failure message
-     */
-    @Nullable public String getMessage() {
-        return message;
-    }
-
-    /**
-     * Get the failure message details.
-     * 
-     * @return failure message details
-     */
-    @Nullable public String getMessageDetail() {
-        return messageDetail;
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthAuthenticator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthAuthenticator.java
deleted file mode 100644
index 20ca5ff3d..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthAuthenticator.java
+++ /dev/null
@@ -1,85 +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.authn.duo.impl;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-
-import javax.annotation.Nonnull;
-
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
-import org.apache.hc.core5.net.URIBuilder;
-
-import com.duosecurity.duoweb.DuoWebException;
-//import javax.json.JsonObject;
-import com.fasterxml.jackson.core.type.TypeReference;
-
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-import net.shibboleth.idp.authn.duo.DuoIntegration;
-import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
-
-/**
- * Implementation of the the Duo AuthAPI /v2/preauth endpoint.
- */
-public class DuoPreauthAuthenticator extends AbstractDuoAuthenticator {
-
-    /** TypeReference for the response generated by the endpoint. */
-    @Nonnull private final TypeReference<DuoResponseWrapper<DuoPreauthResponse>> wrapperTypeRef;
-
-    /** Constructor. */
-    public DuoPreauthAuthenticator() {
-        wrapperTypeRef = new TypeReference<>() {};
-    }
-
-    /**
-     * Perform an authentication action via the Duo AuthAPI /preauth endpoint.
-     * 
-     * @param duoContext Duo authentication context to use
-     * @param duoIntegration Duo integration to use
-     * 
-     * @return a {@link DuoPreauthResponse}
-     * 
-     * @throws DuoWebException if an error occurs
-     */
-    public DuoPreauthResponse authenticate(@Nonnull final DuoAuthenticationContext duoContext,
-            @Nonnull final DuoIntegration duoIntegration) throws DuoWebException {
-        try {
-            // Prepare the request
-            final URI uri = new URIBuilder().setScheme("https").setHost(duoIntegration.getAPIHost())
-                    .setPath("/auth/v2/preauth").build();
-            final ClassicRequestBuilder rb =
-                    ClassicRequestBuilder.post().setUri(uri).addParameter(DuoAuthAPI.DUO_USERNAME, duoContext.getUsername());
-            
-            if (duoContext.getClientAddress() != null) {
-                rb.addParameter(DuoAuthAPI.DUO_IPADDR, duoContext.getClientAddress());
-            }
-            
-            DuoSupport.signRequest(rb, duoIntegration);
-            final ClassicHttpRequest request = rb.build();
-
-            return doAPIRequest(request, wrapperTypeRef).getResponse();
-        } catch (final IOException | URISyntaxException | InvalidKeyException | NoSuchAlgorithmException ex) {
-            throw new DuoWebException("Duo AuthAPI preauth request failed: " + ex.getMessage());
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthResponse.java
deleted file mode 100644
index 53cc918bb..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoPreauthResponse.java
+++ /dev/null
@@ -1,61 +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.authn.duo.impl;
-
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Describes the results of an pre-authentication attempt via the Duo AuthAPI.
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public class DuoPreauthResponse extends DuoAuthAPIResponse {
-
-    /** the {@link List} of {@link DuoDevice}s registered. */
-    @JsonProperty("devices") @Nonnull private List<DuoDevice> devices = new ArrayList<>();
-
-    /** the {@link URL} for the self-enrollment portal. */
-    @JsonProperty("enroll_portal_url") @Nullable private URL enrollPortalURL;
-
-    /**
-     * Get the Duo devices.
-     * 
-     * @return Duo devices
-     */
-    @Nonnull public Collection<DuoDevice> getDevices() {
-        return devices;
-    }
-
-    /**
-     * Get the Duo enrollment portal URL.
-     * 
-     * @return Duo enrollment portal URL
-     */
-    @Nullable public URL getEnrollPortalURL() {
-        return enrollPortalURL;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoResponseWrapper.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoResponseWrapper.java
deleted file mode 100644
index 4aa0d9422..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoResponseWrapper.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.authn.duo.impl;
-
-import javax.annotation.Nonnull;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-/**
- * Handle a generic object returned from the response that will come from the Duo 
- * AuthAPI.
- *
- * @param <T> the subclass of {@link DuoAuthAPIResponse} being wrapped
- */
- at JsonIgnoreProperties(ignoreUnknown = true)
-public class DuoResponseWrapper<T extends DuoAuthAPIResponse> {
-    
-    /** the inner response. */
-    @JsonProperty("response")
-    private T response;
-    
-    /** the response status. */
-    @JsonProperty("stat")
-    private String stat;
-    
-    /**
-     * Get the inner response.
-     * 
-     * @return inner response
-     */
-    @Nonnull public T getResponse() {
-        assert response != null;
-        return response;
-    }
-
-    /**
-     * Get the response status.
-     * 
-     * @return response status
-     */
-    @Nonnull public String getStat() {
-        assert stat != null;
-        return stat;
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoSupport.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoSupport.java
deleted file mode 100644
index 880768951..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/DuoSupport.java
+++ /dev/null
@@ -1,218 +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.authn.duo.impl;
-
-import com.duosecurity.duoweb.Base64;
-import com.duosecurity.duoweb.DuoWeb;
-import com.duosecurity.duoweb.DuoWebException;
-import com.duosecurity.duoweb.Util;
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.idp.authn.duo.DuoIntegration;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.primitive.StringSupport;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import org.apache.hc.core5.http.NameValuePair;
-import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;
-
-/**
- * Helpers for DuoWeb and Duo AuthAPI operations.
- * 
- * @since 3.3.0
- */
-public final class DuoSupport {
-
-    /** RFC 2822 formatter for date/time. */
-    public static final DateTimeFormatter RFC_2822_DATE_FORMAT;
-
-    /** Constructor. */
-    private DuoSupport() {
-    }
-    
-    /**
-     * Created a signed request to Duo for a user.
-     * 
-     * @param duo integration parameters to use
-     * @param username user to authenticate
-     * @return the signed request string
-     * 
-     * @throws DuoWebException if an error occurs
-     */
-    @Nonnull @NotEmpty public static String generateSignedRequestToken(@Nonnull final DuoIntegration duo,
-            @Nonnull @NotEmpty final String username)
-            throws DuoWebException {
-        final String signedRequestToken;
-        
-        if (username == null) {
-            signedRequestToken = DuoWeb.ERR_USER;
-        } else if (duo.getApplicationKey() == null) {
-            signedRequestToken = DuoWeb.ERR_AKEY;
-        } else {
-            signedRequestToken = DuoWeb.signRequest(duo.getIntegrationKey(), duo.getSecretKey(),
-                    duo.getApplicationKey(), username);
-        }
-        if (signedRequestToken.startsWith("ERR|")) {
-            throw new DuoWebException(signedRequestToken);
-        }
-        return signedRequestToken;
-    }
-
-// Checkstyle: ThrowsCount OFF
-    /**
-     * Verify a signed response from Duo and extract the username.
-     * 
-     * @param duo integration parameters to use
-     * @param signedResponseToken    response to validate
-     * 
-     * @return the username from the response
-     * @throws DuoWebException if a Duo failure occurs
-     * @throws InvalidKeyException if a key is invalid
-     * @throws IOException if an I/O error occurs
-     * @throws NoSuchAlgorithmException if the hashing algorithm is unavailable
-     */
-    @Nonnull @NotEmpty public static String validateSignedResponseToken(@Nonnull final DuoIntegration duo,
-            @Nonnull @NotEmpty final String signedResponseToken)
-        throws DuoWebException, InvalidKeyException, IOException, NoSuchAlgorithmException {
-        try {
-            if (duo.getApplicationKey() == null) {
-                throw new DuoWebException(DuoWeb.ERR_AKEY);
-            }
-            final String username = DuoWeb.verifyResponse(duo.getIntegrationKey(), duo.getSecretKey(),
-                    duo.getApplicationKey(), signedResponseToken);
-            assert username != null;
-            return username;
-        } catch (final ArrayIndexOutOfBoundsException e) {
-            // This guard is to prevent an unusual issue being encountered by at least one deployer.
-            throw new DuoWebException(e.getMessage());
-        }
-    }
-// Checkstyle: ThrowsCount ON
- 
-    /**
-     * Sign a Duo AuthAPI request.
-     * 
-     * @param request the request to be signed
-     * @param duo integration parameters to use
-     * 
-     * @throws InvalidKeyException bad skey value
-     * @throws NoSuchAlgorithmException unknown encryption algorithm
-     * @throws UnsupportedEncodingException failure from {@link java.net.URLEncoder}
-     * 
-     * @since 3.4.0
-     */
-    @NotEmpty public static void signRequest(@Nonnull final ClassicRequestBuilder request,
-            @Nonnull final DuoIntegration duo)
-            throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
-        final String ikey = duo.getIntegrationKey();
-        final String skey = duo.getSecretKey();
-        final int sigVersion = 2;
-        final String date = RFC_2822_DATE_FORMAT.format(ZonedDateTime.now());
-        assert date != null;
-        final String canon = canonRequest(request, date, sigVersion);
-        final String sig = Util.hmacSign(skey, canon);
-
-        final String auth = ikey + ":" + sig;
-        final String header = "Basic " + Base64.encodeBytes(auth.getBytes());
-        request.addHeader("Authorization", header);
-        request.addHeader("Date", date);
-    }
-
-    /**
-     * The signature requires that the request parameters being in a particular order as specified in the API.
-     * 
-     * @param request the request
-     * @param date the date
-     * @param sigVersion the signature version
-     * 
-     * @return the parameters to be signed in their canonical order
-     * 
-     * @throws UnsupportedEncodingException failure from {@link java.net.URLEncoder}
-     */
-    private static String canonRequest(@Nonnull final ClassicRequestBuilder request, @Nonnull final String date,
-            final int sigVersion) throws UnsupportedEncodingException {
-        final URI uri = request.getUri();
-        String canon = "";
-        if (sigVersion == 2) {
-            canon += date + "\n";
-        }
-        canon += request.getMethod().toUpperCase() + "\n";
-        canon += uri.getHost().toLowerCase() + "\n";
-        canon += uri.getPath() + "\n";
-        final List<NameValuePair> parms = request.getParameters();
-        assert parms != null;
-        canon += createQueryString(parms);
-
-        return canon;
-    }
-
-    /**
-     * Builds a string representation of the query string with the parameter names is alphabetical order. The names and
-     * values are URL encoded and then they are concatenated with '&' in between.
-     * 
-     * @param params the name/value pairs to be joined
-     * 
-     * @return the canonical query string
-     * 
-     * @throws UnsupportedEncodingException failure from {@link java.net.URLEncoder}
-     */
-    private static String createQueryString(@Nonnull final List<NameValuePair> params)
-            throws UnsupportedEncodingException {
-
-        final ArrayList<String> args = new ArrayList<>();
-
-        // sort by name
-        Collections.sort(params, new Comparator<NameValuePair>() {
-            public int compare(final NameValuePair nvp1, final NameValuePair nvp2) {
-                return nvp1.getName().compareTo(nvp2.getName());
-            }
-        });
-
-        // URL encode and join the name/values with '='
-        final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
-        for (final NameValuePair nvp : params) {
-            final String name = escaper.escape(nvp.getName()).replace("+", "%20").replace("*", "%2A")
-                    .replace("%7E", "~");
-            final String value = escaper.escape(nvp.getValue()).replace("+", "%20").replace("*", "%2A")
-                    .replace("%7E", "~");
-            args.add(name + "=" + value);
-        }
-
-        // Concatenate everything togther with '&'
-        return StringSupport.listToStringValue(args, "&");
-    }
-    
-    static {
-        RFC_2822_DATE_FORMAT = DateTimeFormatter.ofPattern("EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'Z");
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeaders.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeaders.java
deleted file mode 100644
index 497beedfd..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeaders.java
+++ /dev/null
@@ -1,263 +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.authn.duo.impl;
-
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
-import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.servlet.HttpServletSupport;
-
-import jakarta.servlet.http.HttpServletRequest;
-
-/**
- * An action that extracts the Duo factor and device or passcode from HTTP request headers into a
- * {@link DuoAuthenticationContext}, and attaches it to the {@link AuthenticationContext}.
-
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @pre
- *      <pre>
- *      ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null
- *      </pre>
- * 
- * @post If getHttpServletRequest() != null, the content of the headers are checked.
- *      The information found will be attached via a {@link DuoAuthenticationContext}.
- */
-public class ExtractDuoAuthenticationFromHeaders extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractDuoAuthenticationFromHeaders.class);
-
-    /** Whether "auto" should be the default for factor and device. */
-    private boolean autoAuthenticationSupported;
-    
-    /** Whether to trust, and extract, the client address. */
-    private boolean clientAddressTrusted;
-
-    /** Header name for factor. */
-    @Nonnull @NotEmpty private String factorHeaderName;
-
-    /** Header name for device. */
-    @Nonnull @NotEmpty private String deviceHeaderName;
-
-    /** Header name for passcode. */
-    @Nonnull @NotEmpty private String passcodeHeaderName;
-    
-    /** Strategy function for populating pushinfo AuthAPI parameter. */
-    @Nullable private Function<ProfileRequestContext,Map<String,String>> pushInfoLookupStrategy;
-
-    /** Constructor. */
-    ExtractDuoAuthenticationFromHeaders() {
-        autoAuthenticationSupported = true;
-        clientAddressTrusted = true;
-
-        factorHeaderName = DuoAuthAPI.DUO_FACTOR_HEADER_NAME;
-        deviceHeaderName = DuoAuthAPI.DUO_DEVICE_HEADER_NAME;
-        passcodeHeaderName = DuoAuthAPI.DUO_PASSCODE_HEADER_NAME;
-    }
-
-    /**
-     * Set the factor header name.
-     * 
-     * @param headerName the factor header name
-     */
-    public void setFactorHeader(@Nonnull @NotEmpty final String headerName) {
-        checkSetterPreconditions();
-        factorHeaderName = Constraint.isNotNull(StringSupport.trimOrNull(headerName),
-                "Factor header name cannot be null or empty.");
-    }
-
-    /**
-     * Set the device header name.
-     * 
-     * @param headerName the factor header name
-     */
-    public void setDeviceHeader(@Nonnull @NotEmpty final String headerName) {
-        checkSetterPreconditions();
-        deviceHeaderName = Constraint.isNotNull(StringSupport.trimOrNull(headerName),
-                "Device header name cannot be null or empty.");
-    }
-
-    /**
-     * Set the passcode header name.
-     * 
-     * @param headerName the factor header name
-     */
-    public void setPasscodeHeader(@Nonnull @NotEmpty final String headerName) {
-        checkSetterPreconditions();
-        passcodeHeaderName = Constraint.isNotNull(StringSupport.trimOrNull(headerName),
-                "Passcode header name cannot be null or empty.");
-    }
-
-    /**
-     * Get whether the client address should be trusted for use in API calls.
-     * 
-     * @return whether client address should be trusted
-     */
-    public boolean isClientAddressTrusted() {
-        return clientAddressTrusted;
-    }
-    
-    /**
-     * Set whether the client address should be trusted for use in API calls.
-     * 
-     * @param flag flag to set
-     */
-    public void setClientAdddressTrusted(final boolean flag) {
-        checkSetterPreconditions();
-        clientAddressTrusted = flag;
-    }
-    
-    /**
-     * Get whether "auto" is the default setting.
-     * 
-     * @return whether "auto" is the default setting
-     */
-    public boolean isAutoAuthenticationSupported() {
-        return autoAuthenticationSupported;
-    }
-
-    /**
-     * Set whether "auto" is the default setting.
-     * 
-     * @param flag flag to set
-     */
-    public void setAutoAuthenticationSupported(final boolean flag) {
-        checkSetterPreconditions();
-        autoAuthenticationSupported = flag;
-    }
-    
-    /**
-     * Set lookup strategy for AuthAPI pushinfo parameter.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setPushInfoLookupStrategy(
-            @Nullable final Function<ProfileRequestContext,Map<String,String>> strategy) {
-        checkSetterPreconditions();
-        pushInfoLookupStrategy = strategy;
-    }
-
-// Checkstyle: CyclomaticComplexity OFF
-    /** {@inheritDoc} */
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        log.debug("{} Checking for Duo authentication headers", getLogPrefix());
-        
-        final DuoAuthenticationContext duoCtx = new DuoAuthenticationContext();
-        
-        extractHeaders(duoCtx);
-
-        if (duoCtx.getFactor() == null) {
-            if (autoAuthenticationSupported && !profileRequestContext.isBrowserProfile()) {
-                log.debug("{} Non-browser request with no Duo factor specified, enabling auto method", getLogPrefix());
-                duoCtx.setFactor(DuoAuthAPI.DUO_FACTOR_AUTO);
-            } else {
-                log.debug("{} No Duo factor specified, auto method will not be attempted", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-                return;
-            }
-        }
-        
-        // Check for missing passcode.
-        if (DuoAuthAPI.DUO_FACTOR_PASSCODE.equals(duoCtx.getFactor())) {
-            if (duoCtx.getPasscode() == null) {
-                log.warn("{} Request for passcode-based Duo login with no password supplied", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-                return;
-            }
-        } else if (autoAuthenticationSupported && duoCtx.getDeviceID() == null) {
-            // Set auto device if needed.
-            duoCtx.setDeviceID(DuoAuthAPI.DUO_DEVICE_AUTO);
-        }
-
-        // Populate pushinfo either customized or just with service name.
-        if (pushInfoLookupStrategy != null) {
-            final Map<String,String> pushinfo = pushInfoLookupStrategy.apply(profileRequestContext);
-            if (pushinfo != null) {
-                duoCtx.getPushInfo().putAll(pushinfo);
-            }
-        } else {
-            final RelyingPartyUIContext uiCtx = authenticationContext.getSubcontext(RelyingPartyUIContext.class);
-            if (uiCtx != null) {
-                final String name = uiCtx.getServiceName();
-                if (name != null) {
-                    duoCtx.getPushInfo().put("service", uiCtx.getServiceName());
-                }
-            }
-        }
-        
-        authenticationContext.addSubcontext(duoCtx, true);
-
-        log.debug("{} Duo AuthAPI parameters extracted from request (Factor: {}, Device: {}, Passcode: {})",
-                getLogPrefix(), duoCtx.getFactor(), duoCtx.getDeviceID(),
-                duoCtx.getPasscode() != null ? "set" : "not set");
-    }
- // Checkstyle: CyclomaticComplexity ON
-    
-    /**
-     * Extracts the Duo API arguments passed in via the request headers.
-     * 
-     * @param context the DuoApiAuthContext to store the parameters in
-     */
-    protected void extractHeaders(@Nonnull final DuoAuthenticationContext context) {
-
-        final HttpServletRequest httpRequest = getHttpServletRequest();
-        if (httpRequest == null) {
-            return;
-        }
-        
-        if (clientAddressTrusted) {
-            context.setClientAddress(HttpServletSupport.getRemoteAddr(httpRequest));
-        }
-        
-        final String factor = httpRequest.getHeader(factorHeaderName);
-        if (factor != null && !factor.isEmpty()) {
-            context.setFactor(factor);
-        }
-
-        final String device = httpRequest.getHeader(deviceHeaderName);
-        if (device != null && !device.isEmpty()) {
-            context.setDeviceID(device);
-        }
-
-        final String passcode = httpRequest.getHeader(passcodeHeaderName);
-        if (passcode != null && !passcode.isEmpty()) {
-            context.setPasscode(passcode);
-        }
-    }
-
-}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoAuthAPI.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoAuthAPI.java
deleted file mode 100644
index 1722f20e7..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoAuthAPI.java
+++ /dev/null
@@ -1,366 +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.authn.duo.impl;
-
-
-import java.security.Principal;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.security.auth.Subject;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.duosecurity.duoweb.DuoWebException;
-
-import net.shibboleth.idp.authn.AbstractValidationAction;
-import net.shibboleth.idp.authn.AuthnAuditFields;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-import net.shibboleth.idp.authn.duo.DuoIntegration;
-import net.shibboleth.idp.authn.duo.DuoPrincipal;
-import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
-import net.shibboleth.idp.authn.impl.AbstractAuditingValidationAction;
-import net.shibboleth.idp.profile.IdPAuditFields;
-import net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.FunctionSupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * An action that checks for a {@link DuoAuthenticationContext} and directly produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} based on that identity by authenticating against the Duo
- * AuthAPI.
- * 
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @event {@link AuthnEventIds#AUTHN_EXCEPTION}
- * @event {@link AuthnEventIds#ACCOUNT_LOCKED}
- * @event {@link AuthnEventIds#ACCOUNT_WARNING}
- * @event {@link AuthnEventIds#ACCOUNT_ERROR}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @pre
- * 
- *      <pre>
- *      ProfileRequestContext.getSubcontext(AuthenticationContext.class).getAttemptedFlow() != null
- *      </pre>
- * 
- * @post If AuthenticationContext.getSubcontext(DuoAuthenticationContext.class) != null, then an
- *       {@link net.shibboleth.idp.authn.AuthenticationResult} is saved to the {@link AuthenticationContext} on a
- *       successful login. On a failed login, the
- *       {@link AbstractValidationAction#handleError(ProfileRequestContext, AuthenticationContext, String, String)}
- *       method is called.
- */
-public class ValidateDuoAuthAPI extends AbstractAuditingValidationAction {
-
-    /** Default prefix for metrics. */
-    @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.duo";
-
-    /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(ValidateDuoAuthAPI.class);
-
-    /** Lookp strategy for Duo integration. */
-    @Nonnull private Function<ProfileRequestContext,DuoIntegration> duoIntegrationLookupStrategy;
-
-    /** Lookup strategy for username to match against Duo identity. */
-    @Nonnull private Function<ProfileRequestContext,String> usernameLookupStrategy;
-
-    /** Implementation of Duo AuthApi /auth endpoint. */
-    @NonnullAfterInit private DuoAuthAuthenticator authAuthenticator;
-
-    /** Implementation of Duo AuthApi /preauth enpoint. */
-    @NonnullAfterInit private DuoPreauthAuthenticator preauthAuthenticator;
-
-    /** DuoApi context for tokens. Non-Null after preExecute **/
-    @NonnullBeforeExec @NotEmpty private DuoAuthenticationContext duoContext;
-
-    /** Duo integration to use. */
-    @NonnullBeforeExec private DuoIntegration duoIntegration;
-
-    /** Attempted username. */
-    @NonnullBeforeExec @NotEmpty private String username;
-
-    /** Constructor. */
-    public ValidateDuoAuthAPI() {
-        duoIntegrationLookupStrategy = FunctionSupport.constant(null);
-        usernameLookupStrategy = new CanonicalUsernameLookupStrategy();
-        setMetricName(DEFAULT_METRIC_NAME);
-    }
-
-    /**
-     * Set DuoIntegration lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setDuoIntegrationLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,DuoIntegration> strategy) {
-        checkSetterPreconditions();
-        duoIntegrationLookupStrategy = Constraint.isNotNull(strategy, "DuoIntegration lookup strategy cannot be null");
-    }
-
-    /**
-     * Set DuoIntegration details to use directly.
-     * 
-     * @param duo Duo integration details
-     */
-    public void setDuoIntegration(@Nonnull final DuoIntegration duo) {
-        checkSetterPreconditions();
-        Constraint.isNotNull(duo, "DuoIntegration cannot be null");
-        duoIntegrationLookupStrategy = FunctionSupport.constant(duo);
-    }
-
-    /**
-     * Set the lookup strategy to use for the username to match against Duo identity.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setUsernameLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
-        checkSetterPreconditions();
-        usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the {@link DuoAuthAuthenticator}.
-     * 
-     * @param authenticator a Duo AuthAPI /auth endpoint implementation
-     */
-    public void setAuthAuthenticator(@Nonnull final DuoAuthAuthenticator authenticator) {
-        checkSetterPreconditions();
-        authAuthenticator = Constraint.isNotNull(authenticator, "DuoAuthAuthenticator cannot be null");
-    }
-
-    /**
-     * Set the {@link DuoPreauthAuthenticator}.
-     * 
-     * @param authenticator a Duo AuthAPI /preauth endpoint implementation
-     */
-    public void setPreauthAuthenticator(@Nonnull final DuoPreauthAuthenticator authenticator) {
-        checkSetterPreconditions();
-        preauthAuthenticator = Constraint.isNotNull(authenticator, "DuoPreauthAuthenticator cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-
-        if (authAuthenticator == null) {
-            throw new ComponentInitializationException("DuoAuthAuthenticator cannot be null");
-        }
-
-        if (preauthAuthenticator == null) {
-            throw new ComponentInitializationException("DuoPreauthAuthenticator cannot be null");
-        }
-
-    }
-
-    /** {@inheritDoc} */
-    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
-            return false;
-        }
-
-        duoIntegration = duoIntegrationLookupStrategy.apply(profileRequestContext);
-        if (duoIntegration == null) {
-            log.warn("{} No DuoIntegration returned by lookup strategy", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-
-        username = usernameLookupStrategy.apply(profileRequestContext);
-        if (username == null) {
-            log.warn("{} No principal name available to cross-check Duo result", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
-            return false;
-        }
-
-        duoContext = authenticationContext.getSubcontext(DuoAuthenticationContext.class);
-        if (duoContext == null) {
-            log.info("{} No DuoAuthenticationContext available", getLogPrefix());
-            handleError(profileRequestContext, authenticationContext, "No DuoAuthenticationContext context available",
-                    AuthnEventIds.INVALID_AUTHN_CTX);
-            recordFailure(profileRequestContext);
-            return false;
-        } else if (duoContext.getFactor() == null) {
-            log.info("{} No factor set in DuoAuthenticationContext", getLogPrefix());
-            handleError(profileRequestContext, authenticationContext, "No Duo factor set in DuoAuthenticationContext",
-                    AuthnEventIds.REQUEST_UNSUPPORTED);
-            recordFailure(profileRequestContext);
-            return false;
-        }
-
-        duoContext.setUsername(username);
-
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    // CheckStyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        log.trace("{} Attempting Duo AuthAPI authentication", getLogPrefix());
-        
-        try {
-            // Duo AuthAPI pre-authentication
-            final DuoPreauthResponse preAuthResponse = preauthAuthenticator.authenticate(duoContext, duoIntegration);
-            if (preAuthResponse == null) {
-                log.info("{} No Duo AuthAPI preauthentication response", getLogPrefix());
-                throw new DuoWebException("No preauthentication response");
-            }
-
-            final String preAuthResult = preAuthResponse.getResult();
-
-            if (DuoAuthAPI.DUO_PREAUTH_RESULT_ALLOW.equals(preAuthResult)) {
-                // User in bypass mode; treat as authenticated.
-                log.info("{} Duo pre-authentication (bypass) succeeded for '{}'", getLogPrefix(), username);
-                recordSuccess(profileRequestContext);
-                buildAuthenticationResult(profileRequestContext, authenticationContext);
-                return;
-            }
-
-            if (!DuoAuthAPI.DUO_PREAUTH_RESULT_AUTH.equals(preAuthResult)) {
-                // Either deny or enroll.
-                log.info("{} Duo pre-authentication failed for '{}': {}", getLogPrefix(), username,
-                        preAuthResponse.getStatusMessage());
-                handleError(profileRequestContext, authenticationContext,
-                        String.format("%s:%s:%s", preAuthResult, username, preAuthResponse.getStatusMessage()),
-                        AuthnEventIds.ACCOUNT_ERROR);
-                recordFailure(profileRequestContext);
-                return;
-            }
-            
-            // Validate device ID specified against the enrolled set.
-            if (duoContext.getDeviceID() != null && !DuoAuthAPI.DUO_DEVICE_AUTO.equals(duoContext.getDeviceID())) {
-                boolean found = false;
-                for (final DuoDevice device : preAuthResponse.getDevices()) {
-                    final String deviceId = duoContext.getDeviceID();
-                    assert deviceId != null;
-                    if (deviceId.equals(device.getDevice())) {
-                        found = true;
-                        break;
-                    } else if (deviceId.equals(device.getName())) {
-                        log.debug("{} Remapped device ID based on device name ({}) for '{}'", getLogPrefix(),
-                                device.getName(), username);
-                        duoContext.setDeviceID(device.getDevice());
-                        found = true;
-                        break;
-                    }
-                }
-                if (!found) {
-                    log.info("{} Duo authentication failed for '{}': non-existent device ID ({})", getLogPrefix(),
-                            username, duoContext.getDeviceID());
-                    handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
-                            AuthnEventIds.INVALID_CREDENTIALS);
-                    recordFailure(profileRequestContext);
-                    return;
-                }
-            }
-
-            // Duo AuthAPI authentication
-            final DuoAuthResponse authenticationResponse = authAuthenticator.authenticate(duoContext, duoIntegration);
-            if (authenticationResponse == null) {
-                log.info("{} No Duo AuthAPI authentication response", getLogPrefix());
-                throw new DuoWebException("No authentication response");
-            }
-
-            final String authResult = authenticationResponse.getResult();
-            if (DuoAuthAPI.DUO_AUTH_RESULT_ALLOW.equals(authResult)) {
-                log.info("{} Duo authentication succeeded for '{}' (Factor: {}, Device: {})", getLogPrefix(), username,
-                        duoContext.getFactor(), duoContext.getDeviceID());
-                recordSuccess(profileRequestContext);
-                buildAuthenticationResult(profileRequestContext, authenticationContext);
-            } else if (DuoAuthAPI.DUO_AUTH_RESULT_DENY.equals(authResult)) {
-                log.info("{} Duo authentication failed for '{}'", getLogPrefix(), username);
-                handleError(profileRequestContext, authenticationContext, authenticationResponse.getStatus(),
-                        AuthnEventIds.INVALID_CREDENTIALS);
-                recordFailure(profileRequestContext);
-            } else {
-                throw new DuoWebException("Unexpected authentication response");
-            }
-        } catch (final DuoWebException e) {
-            log.error("{} Duo AuthAPI access failed for '{}'", getLogPrefix(), username, e);
-            handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.AUTHN_EXCEPTION);
-            recordFailure(profileRequestContext);
-        }
-    }
-    // CheckStyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
-
-    /** {@inheritDoc} */
-    @Override protected @Nonnull Subject populateSubject(@Nonnull final Subject subject) {
-        assert isPreExecuteCalled();
-        final DuoPrincipal princ = new DuoPrincipal(username);
-        subject.getPrincipals().add(princ);
-        final Set<Principal> princs = duoIntegration.getSupportedPrincipals(Principal.class);
-        subject.getPrincipals().addAll(princs);
-        return subject;
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void buildAuthenticationResult(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-        super.buildAuthenticationResult(profileRequestContext, authenticationContext);
-
-        // Bypass c14n. We already operate on a canonical name, so just re-confirm it.
-        profileRequestContext.ensureSubcontext(SubjectCanonicalizationContext.class).setPrincipalName(username);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nullable @Unmodifiable @NotLive protected Map<String,String> getAuditFields(
-            @Nonnull final ProfileRequestContext profileRequestContext) {
-        final Map<String,String> fields = new HashMap<>();
-        
-        if (username != null) {
-            fields.put(IdPAuditFields.USERNAME, username);
-        }
-        
-        if (duoIntegration != null) {
-            fields.put(AuthnAuditFields.DUO_CLIENT_ID, duoIntegration.getIntegrationKey());
-        }
-        final DuoAuthenticationContext duoCtx = duoContext;
-        if (duoCtx != null) {
-            if (duoCtx.getDeviceID() != null) {
-                fields.put(AuthnAuditFields.DUO_DEVICE_ID, duoCtx.getDeviceID());
-            }
-            if (duoCtx.getFactor() != null) {
-                fields.put(AuthnAuditFields.DUO_FACTOR, duoCtx.getFactor());
-            }
-        }
-        
-        return CollectionSupport.copyToMap(fields);
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoWebResponse.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoWebResponse.java
deleted file mode 100644
index 2530e1bb3..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/ValidateDuoWebResponse.java
+++ /dev/null
@@ -1,255 +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.authn.duo.impl;
-
-import java.io.IOException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.security.auth.Subject;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.duosecurity.duoweb.DuoWebException;
-
-import net.shibboleth.idp.authn.AuthnAuditFields;
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
-import net.shibboleth.idp.authn.duo.DuoIntegration;
-import net.shibboleth.idp.authn.duo.DuoPrincipal;
-import net.shibboleth.idp.authn.impl.AbstractAuditingValidationAction;
-import net.shibboleth.idp.profile.IdPAuditFields;
-import net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.FunctionSupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-import jakarta.servlet.ServletRequest;
-
-/**
- * An action that validates a DuoWeb response message and produces an
- * {@link net.shibboleth.idp.authn.AuthenticationResult} or records error state.
- * 
- * <p>The username to cross-check comes from a lookup strategy, by default a {@link CanonicalUsernameLookupStrategy}
- * that returns a username produced by an earlier authentication flow, and on success the same name is populated into
- * a {@link SubjectCanonicalizationContext} as a pre-established result for the login flow.</p>
- *  
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
- * @event {@link AuthnEventIds#NO_CREDENTIALS}
- * @post ProfileRequestContext.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName() != null
- * 
- * @since 3.3.0
- */
-public class ValidateDuoWebResponse extends AbstractAuditingValidationAction {
-
-    /** Signed response parameter name. */
-    @Nonnull @NotEmpty public static final String RESPONSE_PARAM = "sig_response";
-
-    /** Default prefix for metrics. */
-    @Nonnull @NotEmpty private static final String DEFAULT_METRIC_NAME = "net.shibboleth.idp.authn.duo";
-        
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ValidateDuoWebResponse.class);
-    
-    /** Lookp strategy for Duo integration. */
-    @Nonnull private Function<ProfileRequestContext,DuoIntegration> duoIntegrationLookupStrategy;
-    
-    /** Lookup strategy for username to match against Duo identity. */
-    @Nonnull private Function<ProfileRequestContext,String> usernameLookupStrategy;
-    
-    /** Duo integration to use. */
-    @NonnullBeforeExec private DuoIntegration duoIntegration;
-    
-    /** Attempted username. */
-    @NonnullBeforeExec @NotEmpty private String username;
-    
-    /** Signed response string. */
-    @NonnullBeforeExec @NotEmpty private String signedResponse;
-    
-    /** Constructor. */
-    public ValidateDuoWebResponse() {
-        duoIntegrationLookupStrategy = FunctionSupport.constant(null);
-        usernameLookupStrategy = new CanonicalUsernameLookupStrategy();
-        setMetricName(DEFAULT_METRIC_NAME);
-    }
-
-    /**
-     * Set DuoIntegration lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setDuoIntegrationLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,DuoIntegration> strategy) {
-        checkSetterPreconditions();
-        duoIntegrationLookupStrategy = Constraint.isNotNull(strategy, "DuoIntegration lookup strategy cannot be null");
-    }
-
-    /**
-     * Set DuoIntegration details to use directly.
-     * 
-     * @param duo Duo integration details
-     */
-    public void setDuoIntegration(@Nonnull final DuoIntegration duo) {
-        checkSetterPreconditions();
-        Constraint.isNotNull(duo, "DuoIntegration cannot be null");
-        duoIntegrationLookupStrategy = FunctionSupport.constant(duo);
-    }
-    
-    /**
-     * Set the lookup strategy to use for the username to match against Duo identity.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setUsernameLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
-        checkSetterPreconditions();
-        usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-        
-        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
-            return false;
-        }
-        
-        duoIntegration = duoIntegrationLookupStrategy.apply(profileRequestContext);
-        if (duoIntegration == null) {
-            log.warn("{} No DuoIntegration returned by lookup strategy", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-
-        username = usernameLookupStrategy.apply(profileRequestContext);
-        if (username == null) {
-            log.warn("{} No principal name available to cross-check Duo result", getLogPrefix());
-            handleError(profileRequestContext, authenticationContext, AuthnEventIds.NO_CREDENTIALS,
-                    AuthnEventIds.NO_CREDENTIALS);
-            return false;
-        }
-
-        final ServletRequest servletRequest = getHttpServletRequest();
-        if (servletRequest == null) {
-            log.error("{} No ServletRequest available", getLogPrefix());
-            handleError(profileRequestContext, authenticationContext, AuthnEventIds.NO_CREDENTIALS,
-                    AuthnEventIds.NO_CREDENTIALS);
-            return false;
-        }
-        
-        signedResponse = servletRequest.getParameter(RESPONSE_PARAM);
-        if (signedResponse == null || signedResponse.isEmpty()) {
-            log.warn("{} No signed Duo response in the request", getLogPrefix());
-            handleError(profileRequestContext, authenticationContext, AuthnEventIds.NO_CREDENTIALS,
-                    AuthnEventIds.NO_CREDENTIALS);
-            recordFailure(profileRequestContext);
-            return false;
-        }
-                        
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        log.trace("{} Signed Duo response is: {}", getLogPrefix(), signedResponse);
-                
-        final String usernameFromDuo;
-        try {
-            usernameFromDuo = DuoSupport.validateSignedResponseToken(duoIntegration, signedResponse);
-        } catch (final InvalidKeyException | NoSuchAlgorithmException | DuoWebException | IOException e) {
-            log.warn("{} Error validating signed Duo response for username '{}'", getLogPrefix(), username, e);
-            handleError(profileRequestContext, authenticationContext, e, AuthnEventIds.INVALID_CREDENTIALS);
-            recordFailure(profileRequestContext);
-            return;
-        }
-        if (!username.equals(usernameFromDuo)) {
-            log.warn("{} Username '{}' from Duo response does not match previously established username '{}'",
-                    getLogPrefix(), usernameFromDuo, username);
-            handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
-                    AuthnEventIds.INVALID_CREDENTIALS);
-            recordFailure(profileRequestContext);
-        } else {
-            log.info("{} Duo authentication succeeded for '{}'", getLogPrefix(), usernameFromDuo);
-            recordSuccess(profileRequestContext);
-            buildAuthenticationResult(profileRequestContext, authenticationContext);
-        }
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected @Nonnull Subject populateSubject(@Nonnull final Subject subject) {
-
-        assert isPreExecuteCalled();
-        final DuoPrincipal princ = new DuoPrincipal(username);
-        subject.getPrincipals().add(princ);
-        final Set<Principal> princs = duoIntegration.getSupportedPrincipals(Principal.class);
-        subject.getPrincipals().addAll(princs);
-
-        return subject;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    protected void buildAuthenticationResult(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-        super.buildAuthenticationResult(profileRequestContext, authenticationContext);
-        
-        // Bypass c14n. We already operate on a canonical name, so just re-confirm it.
-        profileRequestContext.ensureSubcontext(SubjectCanonicalizationContext.class).setPrincipalName(username);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nullable @Unmodifiable @NotLive protected Map<String,String> getAuditFields(
-            @Nonnull final ProfileRequestContext profileRequestContext) {
-        
-        if (username != null) {
-            if (duoIntegration != null) {
-                return Map.of(AuthnAuditFields.DUO_CLIENT_ID, duoIntegration.getIntegrationKey(),
-                        IdPAuditFields.USERNAME, username);
-            } else {
-                return CollectionSupport.singletonMap(IdPAuditFields.USERNAME, username);
-            }
-        } else if (duoIntegration != null) {
-            return CollectionSupport.singletonMap(AuthnAuditFields.DUO_CLIENT_ID, duoIntegration.getIntegrationKey());
-        }
-        
-        return super.getAuditFields(profileRequestContext);
-    }
-    
-}
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/package-info.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/package-info.java
deleted file mode 100644
index d8616a9b3..000000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/duo/impl/package-info.java
+++ /dev/null
@@ -1,24 +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.
- */
-
-/**
- * Implementation of Duo authentication.
- */
- at NonnullElements
-package net.shibboleth.idp.authn.duo.impl;
-
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/DuoSupportTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/DuoSupportTest.java
deleted file mode 100644
index 83db83866..000000000
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/DuoSupportTest.java
+++ /dev/null
@@ -1,33 +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.authn.duo.impl;
-
-import java.time.ZonedDateTime;
-
-import org.testng.annotations.Test;
-
-/**
- * Unit test for Duo support code.
- */
-public class DuoSupportTest {
-
-    @Test public void testDateFormatting() {
-        DuoSupport.RFC_2822_DATE_FORMAT.format(ZonedDateTime.now());
-    }
-
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeadersTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeadersTest.java
deleted file mode 100644
index 4a7d97657..000000000
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/duo/impl/ExtractDuoAuthenticationFromHeadersTest.java
+++ /dev/null
@@ -1,166 +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.authn.duo.impl;
-
-
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
-import net.shibboleth.idp.authn.impl.testing.BaseAuthenticationContextTest;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.testing.ConstantSupplier;
-
-/** {@link ExtractDuoAuthenticationFromHeaders} unit test. */
-public class ExtractDuoAuthenticationFromHeadersTest extends BaseAuthenticationContextTest {
-    
-    private ExtractDuoAuthenticationFromHeaders action; 
-    
-    @BeforeMethod public void setUp() throws ComponentInitializationException {
-        super.setUp();
-        
-        action = new ExtractDuoAuthenticationFromHeaders();
-        final MockHttpServletRequest request = new MockHttpServletRequest();
-        action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
-        action.initialize();
-    }
-    
-    @Test public void testNoServletNoAuto() throws ComponentInitializationException {
-        action = new ExtractDuoAuthenticationFromHeaders();
-        action.setAutoAuthenticationSupported(false);
-        action.initialize();
-        final Event event = action.execute(src);
-        
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
-        
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        Assert.assertNull(duoCtx);
-    }
-
-    @Test public void testNoAuto() throws ComponentInitializationException {
-        action = new ExtractDuoAuthenticationFromHeaders();
-        final MockHttpServletRequest request = new MockHttpServletRequest();
-        action.setHttpServletRequestSupplier(new ConstantSupplier<>(request));
-        action.setAutoAuthenticationSupported(false);
-        action.initialize();
-        final Event event = action.execute(src);
-        
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
-
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        Assert.assertNull(duoCtx);
-    }
-    
-    @Test public void testNoServletAuto() throws ComponentInitializationException {
-        action = new ExtractDuoAuthenticationFromHeaders();
-        action.initialize();
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        assert duoCtx != null;
-        Assert.assertEquals(duoCtx.getFactor(), DuoAuthAPI.DUO_FACTOR_AUTO);
-        Assert.assertEquals(duoCtx.getDeviceID(), DuoAuthAPI.DUO_DEVICE_AUTO);
-        Assert.assertNull(duoCtx.getPasscode());
-    }
-    
-    @Test public void testFactorAutoDevice() {
-        assert action != null;
-        final MockHttpServletRequest request = ((MockHttpServletRequest) action.getHttpServletRequest());
-        assert request != null;
-        request.addHeader(DuoAuthAPI.DUO_FACTOR_HEADER_NAME, DuoAuthAPI.DUO_FACTOR_PUSH);
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        assert duoCtx != null;
-        Assert.assertEquals(duoCtx.getFactor(), DuoAuthAPI.DUO_FACTOR_PUSH);
-        Assert.assertEquals(duoCtx.getDeviceID(), DuoAuthAPI.DUO_DEVICE_AUTO);
-        Assert.assertNull(duoCtx.getPasscode());
-    }
-    
-    @Test public void testDeviceAutoFactor() {
-        assert action != null;
-        final MockHttpServletRequest request = ((MockHttpServletRequest) action.getHttpServletRequest());
-        assert request != null;
-        request.addHeader(DuoAuthAPI.DUO_DEVICE_HEADER_NAME, "foo");
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        assert duoCtx != null;
-        Assert.assertEquals(duoCtx.getFactor(), DuoAuthAPI.DUO_FACTOR_AUTO);
-        Assert.assertEquals(duoCtx.getDeviceID(), "foo");
-        Assert.assertNull(duoCtx.getPasscode());
-    }
-    
-    @Test public void testNoPasscode() {
-        assert action != null;
-        final MockHttpServletRequest request = ((MockHttpServletRequest) action.getHttpServletRequest());
-        assert request != null;
-        request.addHeader(DuoAuthAPI.DUO_FACTOR_HEADER_NAME, DuoAuthAPI.DUO_FACTOR_PASSCODE);
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
-
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        assert duoCtx == null;
-    }
-
-    @Test public void testPasscode() {
-        assert action != null;
-        final MockHttpServletRequest request = ((MockHttpServletRequest) action.getHttpServletRequest());
-        assert request != null;
-        request.addHeader(DuoAuthAPI.DUO_FACTOR_HEADER_NAME, DuoAuthAPI.DUO_FACTOR_PASSCODE);
-        request.addHeader(DuoAuthAPI.DUO_PASSCODE_HEADER_NAME, "foo");
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        assert authCtx != null;
-        final DuoAuthenticationContext duoCtx = authCtx.getSubcontext(DuoAuthenticationContext.class);
-        assert duoCtx != null;
-        Assert.assertEquals(duoCtx.getFactor(), DuoAuthAPI.DUO_FACTOR_PASSCODE);
-        Assert.assertNull(duoCtx.getDeviceID());
-        Assert.assertEquals(duoCtx.getPasscode(), "foo");
-    }
-
-}
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
index fc6b84a77..763539d01 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/authn-system.xml
@@ -326,15 +326,6 @@
         </constructor-arg>
     </bean>
 
-    <!-- TBD: If still needed, likely moved to Duo OIDC plugin. -->
-    <bean p:id="duo" class="net.shibboleth.idp.authn.principal.GenericPrincipalService"
-            c:claz="net.shibboleth.idp.authn.duo.DuoPrincipal">
-        <constructor-arg name="serializer">
-            <bean class="net.shibboleth.idp.authn.principal.SimplePrincipalSerializer"
-                c:claz="net.shibboleth.idp.authn.duo.DuoPrincipal" c:name="DUO" />
-        </constructor-arg>
-    </bean>
-
     <bean p:id="totp" class="net.shibboleth.idp.authn.principal.GenericPrincipalService"
             c:claz="net.shibboleth.idp.authn.principal.TOTPPrincipal">
         <constructor-arg name="serializer">

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


More information about the commits mailing list