[java-identity-provider] branch master updated: IDP-1239 - Non-browser support for Duo authentication

Scott Cantor cantor.2 at osu.edu
Wed Aug 8 19:30:28 EDT 2018


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

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

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

The following commit(s) were added to refs/heads/master by this push:
       new  a0beaa8   IDP-1239 - Non-browser support for Duo authentication
a0beaa8 is described below

commit a0beaa8f4da2d55188f2e9b07104f4f92163e78f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Aug 8 19:30:25 2018 -0400

    IDP-1239 - Non-browser support for Duo authentication
    
    https://issues.shibboleth.net/jira/browse/IDP-1239
    
    Add client address, device ID validation.
---
 .../net/shibboleth/idp/authn/duo/DuoAuthAPI.java   | 11 +++++++-
 .../duo/context/DuoAuthenticationContext.java      | 24 ++++++++++++++++++
 .../idp/authn/duo/impl/DuoAuthAPIResponse.java     | 21 ----------------
 .../idp/authn/duo/impl/DuoAuthAuthenticator.java   |  5 +++-
 .../idp/authn/duo/impl/DuoAuthResponse.java        | 20 ---------------
 .../authn/duo/impl/DuoPreauthAuthenticator.java    |  8 +++++-
 .../idp/authn/duo/impl/DuoPreauthResponse.java     | 20 ---------------
 .../impl/ExtractDuoAuthenticationFromHeaders.java  | 29 ++++++++++++++++++++++
 .../idp/authn/duo/impl/ValidateDuoAuthAPI.java     | 27 +++++++++++++++++---
 9 files changed, 98 insertions(+), 67 deletions(-)

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
index 73ba8b6..eb22e80 100644
--- 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
@@ -29,6 +29,12 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 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. */
@@ -36,7 +42,7 @@ public final class DuoAuthAPI {
 
     /** Duo AuthAPI parameter name. */
     @Nonnull @NotEmpty public static final String DUO_PASSCODE = "passcode";
-
+    
     /** Duo AuthAPI factor "auto" value. */
     @Nonnull @NotEmpty public static final String DUO_FACTOR_AUTO = "auto";
 
@@ -76,6 +82,9 @@ public final class DuoAuthAPI {
     /** 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";
     
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
index d968b58..696a82b 100644
--- 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
@@ -38,6 +38,9 @@ public class DuoAuthenticationContext extends BaseContext {
     /** Username. */
     @Nullable private String username;
     
+    /** Client address. */
+    @Nullable private String clientAddress;
+    
     /** Factor. */
     @Nullable private String duoFactor;
 
@@ -67,8 +70,29 @@ public class DuoAuthenticationContext extends BaseContext {
         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
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
index a053e5c..7f4195a 100644
--- 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
@@ -56,25 +56,4 @@ public abstract class DuoAuthAPIResponse {
         return statusMessage;
     }
 
-    /**
-     * Check if the result of the response is allow state.
-     * 
-     * @return true if result of the response is allow state
-     */
-    public abstract boolean isAllow();
-    
-    /**
-     * Check if the result of the response is deny state.
-     * 
-     * @return true if result of the response is deny state
-     */
-    public abstract boolean isDeny();
-    
-    /**
-     * Check if the status of the response is bypass state.
-     * 
-     * @return true if status of the response is bypass state
-     */
-    public abstract boolean isBypass();
-
 }
\ 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
index 95c5f86..04523ca 100644
--- 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
@@ -67,7 +67,10 @@ public class DuoAuthAuthenticator extends AbstractDuoAuthenticator {
             final URI uri = new URIBuilder().setScheme("https").setHost(duoIntegration.getAPIHost())
                     .setPath("/auth/v2/auth").build();
             final RequestBuilder rb =
-                    RequestBuilder.post().setUri(uri).addParameter("username", duoContext.getUsername());
+                    RequestBuilder.post().setUri(uri).addParameter(DuoAuthAPI.DUO_USERNAME, duoContext.getUsername());
+            if (duoContext.getClientAddress() != null) {
+                rb.addParameter(DuoAuthAPI.DUO_IPADDR, duoContext.getClientAddress());
+            }
             if (duoContext.getFactor() != null) {
                 rb.addParameter(DuoAuthAPI.DUO_FACTOR, duoContext.getFactor());
             }
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
index d7a4510..8a14dce 100644
--- 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
@@ -23,8 +23,6 @@ import javax.annotation.Nullable;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-
 /**
  * Describes the results of an authentication attempt via the Duo AuthAPI, intended for use with a jackson
  * {@link ObjectMapper}.
@@ -56,22 +54,4 @@ public class DuoAuthResponse extends DuoAuthAPIResponse {
         return trustedDeviceToken;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public boolean isAllow() {
-        return getResult().equals(DuoAuthAPI.DUO_AUTH_RESULT_ALLOW);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isDeny() {
-        return getResult().equals(DuoAuthAPI.DUO_AUTH_RESULT_DENY);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isBypass() {
-        return getStatus().equals(DuoAuthAPI.DUO_AUTH_STATUS_BYPASS);
-    }
-
 }
\ 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
index 29982b6..060d8d6 100644
--- 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
@@ -33,6 +33,7 @@ import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.methods.RequestBuilder;
 import org.apache.http.client.utils.URIBuilder;
 
+import net.shibboleth.idp.authn.duo.DuoAuthAPI;
 import net.shibboleth.idp.authn.duo.DuoIntegration;
 import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
 
@@ -66,7 +67,12 @@ public class DuoPreauthAuthenticator extends AbstractDuoAuthenticator {
             final URI uri = new URIBuilder().setScheme("https").setHost(duoIntegration.getAPIHost())
                     .setPath("/auth/v2/preauth").build();
             final RequestBuilder rb =
-                    RequestBuilder.post().setUri(uri).addParameter("username", duoContext.getUsername());
+                    RequestBuilder.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 HttpUriRequest request = rb.build();
 
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
index 3c349f9..75b77bf 100644
--- 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
@@ -27,8 +27,6 @@ import javax.annotation.Nullable;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 
-import net.shibboleth.idp.authn.duo.DuoAuthAPI;
-
 /**
  * Describes the results of an pre-authentication attempt via the Duo AuthAPI.
  */
@@ -59,22 +57,4 @@ public class DuoPreauthResponse extends DuoAuthAPIResponse {
         return enrollPortalURL;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public boolean isAllow() {
-        return getResult().equals(DuoAuthAPI.DUO_AUTH_RESULT_ALLOW);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isDeny() {
-        return getResult().equals(DuoAuthAPI.DUO_AUTH_RESULT_DENY);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean isBypass() {
-        return getResult().equals(DuoAuthAPI.DUO_PREAUTH_RESULT_ALLOW);
-    }
-
 }
\ 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
index 7a91ec7..5ad90dd 100644
--- 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
@@ -61,6 +61,9 @@ public class ExtractDuoAuthenticationFromHeaders<InboundMessageType,OutboundMess
 
     /** 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;
@@ -74,6 +77,7 @@ public class ExtractDuoAuthenticationFromHeaders<InboundMessageType,OutboundMess
     /** Constructor. */
     ExtractDuoAuthenticationFromHeaders() {
         autoAuthenticationSupported = true;
+        clientAddressTrusted = true;
 
         factorHeaderName = DuoAuthAPI.DUO_FACTOR_HEADER_NAME;
         deviceHeaderName = DuoAuthAPI.DUO_DEVICE_HEADER_NAME;
@@ -117,6 +121,26 @@ public class ExtractDuoAuthenticationFromHeaders<InboundMessageType,OutboundMess
     }
 
     /**
+     * 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) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        clientAddressTrusted = flag;
+    }
+    
+    /**
      * Get whether "auto" is the default setting.
      * 
      * @return whether "auto" is the default setting
@@ -154,6 +178,7 @@ public class ExtractDuoAuthenticationFromHeaders<InboundMessageType,OutboundMess
         log.debug("{} Checking for Duo authentication headers", getLogPrefix());
         
         final DuoAuthenticationContext duoCtx = new DuoAuthenticationContext();
+        
         extractHeaders(duoCtx);
 
         if (duoCtx.getFactor() == null) {
@@ -198,6 +223,10 @@ public class ExtractDuoAuthenticationFromHeaders<InboundMessageType,OutboundMess
             return;
         }
         
+        if (clientAddressTrusted) {
+            context.setClientAddress(httpRequest.getRemoteAddr());
+        }
+        
         final String factor = httpRequest.getHeader(factorHeaderName);
         if (factor != null && !factor.isEmpty()) {
             context.setFactor(factor);
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
index 6aac899..ba47950 100644
--- 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
@@ -55,8 +55,10 @@ import com.duosecurity.duoweb.DuoWebException;
  * 
  * @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
  * 
@@ -219,7 +221,7 @@ public class ValidateDuoAuthAPI extends AbstractValidationAction {
     }
 
     /** {@inheritDoc} */
-    // CheckStyle: ReturnCount OFF
+    // CheckStyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
 
@@ -253,6 +255,25 @@ public class ValidateDuoAuthAPI extends AbstractValidationAction {
                 recordFailure();
                 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()) {
+                    if (duoContext.getDeviceID().equals(device.getDevice())) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (!found) {
+                    log.info("{} Request specified non-existent device ID ({}) for '{}': {}", getLogPrefix(),
+                            duoContext.getDeviceID(), username, preAuthResponse.getStatusMessage());
+                    handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
+                            AuthnEventIds.INVALID_CREDENTIALS);
+                    recordFailure();
+                    return;
+                }
+            }
 
             // Duo AuthAPI authentication
             final DuoAuthResponse authenticationResponse = authAuthenticator.authenticate(duoContext, duoIntegration);
@@ -268,7 +289,7 @@ public class ValidateDuoAuthAPI extends AbstractValidationAction {
                 buildAuthenticationResult(profileRequestContext, authenticationContext);
             } else if (DuoAuthAPI.DUO_AUTH_RESULT_DENY.equals(authResult)) {
                 log.info("{} Duo authentication failed for '{}'", getLogPrefix(), username);
-                handleError(profileRequestContext, authenticationContext, AuthnEventIds.INVALID_CREDENTIALS,
+                handleError(profileRequestContext, authenticationContext, authenticationResponse.getStatus(),
                         AuthnEventIds.INVALID_CREDENTIALS);
                 recordFailure();
             } else {
@@ -280,7 +301,7 @@ public class ValidateDuoAuthAPI extends AbstractValidationAction {
             recordFailure();
         }
     }
-    // CheckStyle: ReturnCount OFF
+    // CheckStyle: CyclomaticComplexity|MethodLength|ReturnCount OFF
 
     /** {@inheritDoc} */
     @Override protected Subject populateSubject(@Nonnull final Subject subject) {

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


More information about the commits mailing list