[java-identity-provider] branch master updated: IDP-1391 - Add a service layer for password validators.

Scott Cantor cantor.2 at osu.edu
Mon Aug 12 14:30:09 EDT 2019


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=5c59c83f204f5e577294f56bedfba7cae75a522a

The following commit(s) were added to refs/heads/master by this push:
       new  5c59c83   IDP-1391 - Add a service layer for password validators.
5c59c83 is described below

commit 5c59c83f204f5e577294f56bedfba7cae75a522a
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Aug 12 14:30:06 2019 -0400

    IDP-1391 - Add a service layer for password validators.
    
    https://issues.shibboleth.net/jira/browse/IDP-1391
    
    Renaming some tests.
    Add htpasswd validator.
---
 .../net/shibboleth/idp/authn/AuthnEventIds.java    |   3 +
 .../authn/impl/HTPasswdCredentialValidator.java    | 259 +++++++++++++++++++++
 ...t.java => HTPasswdCredentialValidatorTest.java} | 199 +++++-----------
 ...STest.java => JAASCredentialValidatorTest.java} |   2 +-
 ...PTest.java => LDAPCredentialValidatorTest.java} |   2 +-
 .../net/shibboleth/idp/authn/impl/htpasswd.txt     |   3 +
 .../resources/conf/authn/password-authn-config.xml |   1 +
 .../system/flows/authn/password-authn-beans.xml    |   5 +
 8 files changed, 334 insertions(+), 140 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
index 524c144..c449094 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
@@ -44,6 +44,9 @@ public final class AuthnEventIds {
     
     /** ID of event returned if there are no credentials available in the request. */
     @Nonnull @NotEmpty public static final String NO_CREDENTIALS = "NoCredentials";
+
+    /** ID of event returned if a username is unknown. */
+    @Nonnull @NotEmpty public static final String UNKNOWN_USERNAME = "UnknownUsername";
     
     /** ID of event returned if the given credentials are invalid. */
     @Nonnull @NotEmpty public static final String INVALID_CREDENTIALS = "InvalidCredentials";
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidator.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidator.java
new file mode 100644
index 0000000..d1ecf48
--- /dev/null
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidator.java
@@ -0,0 +1,259 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import net.shibboleth.idp.authn.AbstractUsernamePasswordCredentialValidator;
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.UsernamePasswordContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.codec.StringDigester;
+import net.shibboleth.utilities.java.support.codec.StringDigester.OutputFormat;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.apache.commons.codec.digest.Crypt;
+import org.apache.commons.codec.digest.Md5Crypt;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+
+import com.google.common.base.Strings;
+
+/**
+ * A password validator that authenticates against Apache htpasswd files.
+ * 
+ * @since 4.0.0
+ */
+ at ThreadSafe
+public class HTPasswdCredentialValidator extends AbstractUsernamePasswordCredentialValidator {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(HTPasswdCredentialValidator.class);
+    
+    /** Digester for SHA-1. */
+    @NonnullAfterInit private StringDigester digester;
+    
+    /** Source of information. */
+    @Nullable private Resource htPasswdResource;
+
+    /** File timestamp. */
+    @Nullable private long lastModified;
+    
+    /** In-memory copy of entries. */
+    @Nonnull @NonnullElements private final Map<String,String> credentialMap;
+    
+    /** Constructor. */
+    public HTPasswdCredentialValidator() {
+        lastModified = 0;
+        credentialMap = new ConcurrentHashMap<>();
+    }
+    
+    /**
+     * Set the resource to use.
+     * 
+     * @param resource resource to use
+     */
+    public void setResource(@Nonnull final Resource resource) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        htPasswdResource = Constraint.isNotNull(resource, "Resource cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        try {
+            if (htPasswdResource == null) {
+                throw new ComponentInitializationException("Resource cannot be null");
+            }
+
+            digester = new StringDigester("SHA1", OutputFormat.BASE64);
+            
+            try (final InputStream is = htPasswdResource.getInputStream()) {
+                credentialMap.putAll(readCredentials(is));
+            }
+
+            if (htPasswdResource.isFile()) {
+                lastModified = htPasswdResource.lastModified();
+            } else {
+                htPasswdResource = null;
+            }
+            
+        } catch (final IOException e) {
+            throw new ComponentInitializationException("Error reading htpasswd resource", e);
+        } catch (final NoSuchAlgorithmException e) {
+            throw new ComponentInitializationException("Error creating digester", e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable protected Subject doValidate(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final AuthenticationContext authenticationContext,
+            @Nonnull final UsernamePasswordContext usernamePasswordContext,
+            @Nullable final WarningHandler warningHandler,
+            @Nullable final ErrorHandler errorHandler) throws Exception {
+        
+        
+        final String passwd = credentialMap.get(usernamePasswordContext.getUsername());
+        if (passwd == null) {
+            log.debug("{} Username '{}' not found in password resource", getLogPrefix(),
+                    usernamePasswordContext.getUsername());
+            final LoginException e = new LoginException(AuthnEventIds.UNKNOWN_USERNAME); 
+            if (errorHandler != null) { 
+                errorHandler.handleError(profileRequestContext, authenticationContext, e,
+                        AuthnEventIds.UNKNOWN_USERNAME);
+            }
+            throw e;
+        }
+
+        log.debug("{} Attempting to authenticate user '{}' ", getLogPrefix(),
+                usernamePasswordContext.getUsername());
+        
+        
+        if (authenticate(usernamePasswordContext, passwd)) {
+            log.info("{} Login by '{}' succeeded", getLogPrefix(), usernamePasswordContext.getUsername());
+            return populateSubject(new Subject(), usernamePasswordContext);
+        }
+        
+        log.info("{} Login by '{}' failed", getLogPrefix(), usernamePasswordContext.getUsername());
+        
+        final LoginException e = new LoginException(AuthnEventIds.INVALID_CREDENTIALS); 
+        if (errorHandler != null) { 
+            errorHandler.handleError(profileRequestContext, authenticationContext, e,
+                    AuthnEventIds.INVALID_CREDENTIALS);
+        }
+        throw e;
+    }
+        
+    /**
+     * Compare input password to stored value.
+     * 
+     * @param usernamePasswordContext input context
+     * @param storedPassword the stored string
+     * 
+     * @return true iff the password matches
+     */
+    @Nonnull private boolean authenticate(@Nonnull final UsernamePasswordContext usernamePasswordContext,
+            @Nonnull final String storedPassword) {
+        
+        refreshCredentials();
+        
+        // test Apache MD5 variant encrypted password
+        if (storedPassword.startsWith("$apr1$")) {
+            if (storedPassword.equals(Md5Crypt.apr1Crypt(usernamePasswordContext.getPassword(), storedPassword))) {
+                return true;
+            }
+        } else if (storedPassword.startsWith("{SHA}")) {
+            if (storedPassword.substring("{SHA}".length()).equals(
+                    digester.apply(usernamePasswordContext.getPassword()))) {
+                return true;
+            }
+        } else if (storedPassword.equals(Crypt.crypt(usernamePasswordContext.getPassword(), storedPassword))) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check for file refresh.
+     */
+    private void refreshCredentials() {
+        if (htPasswdResource == null) {
+            // Nothing to do.
+            return;
+        }
+        
+        try {
+            if (htPasswdResource.isFile() && htPasswdResource.exists()
+                    && (htPasswdResource.lastModified() > lastModified)) {
+                try (final InputStream is = htPasswdResource.getInputStream()) {
+                    credentialMap.clear();
+                    credentialMap.putAll(readCredentials(is));
+                }
+            }
+        } catch (final IOException e) {
+            log.error("{} Error reloading credentials", getLogPrefix(), e);
+        }
+    }
+    
+    /**
+     * Reads the credentials from stream.
+     * 
+     * @param is input stream
+     * 
+     * @return map of credentials
+     */
+    @Nonnull @NonnullElements private Map<String,String> readCredentials(@Nonnull final InputStream is) {
+        
+        final Map<String,String> credentials = new HashMap<>();
+        
+        final Pattern entry = Pattern.compile("^([^:]+):(.+)");
+        try (final Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) {
+            while (scanner.hasNextLine()) {
+                final String line = scanner.nextLine().trim();
+                if (!line.isEmpty() && !line.startsWith("#")) {
+                    final Matcher m = entry.matcher(line);
+                    if (m.matches()) {
+                        final String username = m.group(1);
+                        final String password = m.group(2);
+                        if (Strings.isNullOrEmpty(username)) {
+                            log.warn("{} Skipping line with empty username", getLogPrefix());
+                            continue;
+                        }
+                        if (Strings.isNullOrEmpty(password)) {
+                            log.warn("{} Skipping '{}' user with blank password", getLogPrefix(), username);
+                            continue;
+                        }
+
+                        credentials.put(username.trim(), password.trim());
+                    }
+                }
+            }
+        }
+        
+        log.debug("{} Loaded {} password entries", getLogPrefix(), credentials.size());
+        
+        return credentials;
+    }
+    
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidatorTest.java
similarity index 67%
copy from idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
copy to idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidatorTest.java
index 3757665..589631f 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/HTPasswdCredentialValidatorTest.java
@@ -20,8 +20,6 @@ package net.shibboleth.idp.authn.impl;
 import java.io.File;
 import java.io.IOException;
 import java.security.Principal;
-import java.security.URIParameter;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -39,69 +37,39 @@ import net.shibboleth.idp.authn.principal.TestPrincipal;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
 import net.shibboleth.idp.authn.principal.impl.ExactPrincipalEvalPredicateFactory;
 import net.shibboleth.idp.profile.ActionTestingSupport;
-import net.shibboleth.utilities.java.support.collection.Pair;
-import net.shibboleth.utilities.java.support.net.URISupport;
 
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.webflow.execution.Event;
 import org.testng.Assert;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
-import com.unboundid.ldap.listener.InMemoryDirectoryServer;
-import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
-import com.unboundid.ldap.listener.InMemoryListenerConfig;
-import com.unboundid.ldap.sdk.LDAPException;
+/** Unit test for htpasswd file validation. */
+public class HTPasswdCredentialValidatorTest extends BaseAuthenticationContextTest {
 
-/** Unit test for JAAS validation. */
-public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationContextTest {
-
-    private static final String DATA_PATH = "src/test/resources/net/shibboleth/idp/authn/impl/";
+    private static final String DATA_PATH = "net/shibboleth/idp/authn/impl/";
     
-    private JAASCredentialValidator validator;
+    private HTPasswdCredentialValidator validator;
     
     private ValidateCredentials action;
 
-    private InMemoryDirectoryServer directoryServer;
-
-    /**
-     * Creates an UnboundID in-memory directory server. Leverages LDIF found in test resources.
-     * 
-     * @throws LDAPException if the in-memory directory server cannot be created
-     */
-    @BeforeClass public void setupDirectoryServer() throws LDAPException {
-
-        final InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=shibboleth,dc=net");
-        config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", 10389));
-        config.addAdditionalBindCredentials("cn=Directory Manager", "password");
-        directoryServer = new InMemoryDirectoryServer(config);
-        directoryServer.importFromLDIF(true, DATA_PATH + "loginLDAPTest.ldif");
-        directoryServer.startListening();
-    }
-
-    /**
-     * Shutdown the in-memory directory server.
-     */
-    @AfterClass public void teardownDirectoryServer() {
-        directoryServer.shutDown(true);
-    }
-
     @BeforeMethod public void setUp() throws Exception {
         super.setUp();
 
-        validator = new JAASCredentialValidator();
-        validator.setId("jaastest");
+        validator = new HTPasswdCredentialValidator();
+        validator.setResource(new ClassPathResource(DATA_PATH + "htpasswd.txt"));
+        validator.setId("htpasswdtest");
         
         action = new ValidateCredentials();
         action.setValidators(Collections.singletonList(validator));
         
         final Map<String,Collection<String>> mappings = new HashMap<>();
-        mappings.put("UnknownUsername", Collections.singleton("DN_RESOLUTION_FAILURE"));
-        mappings.put("InvalidPassword", Collections.singleton("INVALID_CREDENTIALS"));
+        mappings.put("InvalidPassword", Collections.singleton(AuthnEventIds.INVALID_CREDENTIALS));
+        mappings.put(AuthnEventIds.UNKNOWN_USERNAME, Collections.singleton(AuthnEventIds.UNKNOWN_USERNAME));
         action.setClassifiedMessages(mappings);
         
         action.setHttpServletRequest(new MockHttpServletRequest());
@@ -136,50 +104,8 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         final Event event = action.execute(src);
         ActionTestingSupport.assertEvent(event, AuthnEventIds.NO_CREDENTIALS);
     }
-
-    @Test public void testNoConfig() throws Exception {
-        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "foo");
-        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "bar");
-
-        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        ac.setAttemptedFlow(authenticationFlows.get(0));
-        
-        validator.initialize();
-        action.initialize();
-
-        doExtract(prc);
-
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
-        AuthenticationErrorContext errorCtx = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertEquals(errorCtx.getExceptions().size(), 1);
-        Assert.assertTrue(errorCtx.getExceptions().get(0) instanceof LoginException);
-    }
-
-    @Test public void testBadConfig() throws Exception {
-        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "foo");
-        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "bar");
-
-        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
-        ac.setAttemptedFlow(authenticationFlows.get(0));
-        validator.setLoginConfigNames(Collections.singletonList("ShibBadAuth"));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
-        validator.initialize();
-        
-        action.initialize();
-
-        doExtract(prc);
-
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_CREDENTIALS);
-        AuthenticationErrorContext errorCtx = ac.getSubcontext(AuthenticationErrorContext.class);
-        Assert.assertEquals(errorCtx.getExceptions().size(), 1);
-        Assert.assertTrue(errorCtx.getExceptions().get(0) instanceof LoginException);
-    }
-
-    @Test public void testUnsupportedConfig() throws Exception {
+    
+    @Test public void testUnsupported() throws Exception {
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "foo");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "bar");
 
@@ -192,11 +118,7 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         rpc.setOperator("exact");
         rpc.setRequestedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
 
-        validator.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
-                Collections.<Principal>singletonList(new TestPrincipal("test2")))));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
+        validator.setSupportedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test2")));
         validator.initialize();
         
         action.initialize();
@@ -232,20 +154,18 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
         
         validator.initialize();
+        
         action.initialize();
 
         doExtract(prc);
 
         final Event event = action.execute(src);
-        ActionTestingSupport.assertEvent(event, "UnknownUsername");
+        ActionTestingSupport.assertEvent(event, AuthnEventIds.UNKNOWN_USERNAME);
         AuthenticationErrorContext errorCtx = ac.getSubcontext(AuthenticationErrorContext.class);
         Assert.assertTrue(errorCtx.getExceptions().get(0) instanceof LoginException);
-        Assert.assertTrue(errorCtx.isClassifiedError("UnknownUsername"));
+        Assert.assertTrue(errorCtx.isClassifiedError(AuthnEventIds.UNKNOWN_USERNAME));
         Assert.assertFalse(errorCtx.isClassifiedError("InvalidPassword"));
     }
 
@@ -255,9 +175,7 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
+        
         validator.initialize();
         
         action.initialize();
@@ -272,16 +190,13 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         Assert.assertTrue(errorCtx.isClassifiedError("InvalidPassword"));
     }
 
-    @Test public void testAuthorized() throws Exception {
+    @Test public void testAuthorizedMD5() throws Exception {
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
         validator.initialize();
         
         action.initialize();
@@ -297,17 +212,14 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
                 .next().getName(), "PETER_THE_PRINCIPAL");
     }
 
-    @Test public void testAuthorizedAndKeep() throws Exception {
+    @Test public void testAuthorizedMD5WithFile() throws Exception {
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
-        validator.setRemoveContextAfterValidation(false);
+        validator.setResource(new FileSystemResource(getCurrentDir() + "/src/test/resources/" + DATA_PATH + "/htpasswd.txt"));
         validator.initialize();
         
         action.initialize();
@@ -317,30 +229,41 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
         
-        Assert.assertNotNull(ac.getSubcontext(UsernamePasswordContext.class));
+        Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
         Assert.assertNotNull(ac.getAuthenticationResult());
         Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(UsernamePrincipal.class).iterator()
                 .next().getName(), "PETER_THE_PRINCIPAL");
     }
 
-    @Test public void testSupported() throws Exception {
-        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL");
+    @Test public void testAuthorizedSHA() throws Exception {
+        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL2");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
+
+        validator.initialize();
         
-        final RequestedPrincipalContext rpc = ac.getSubcontext(RequestedPrincipalContext.class, true);
-        rpc.getPrincipalEvalPredicateFactoryRegistry().register(
-                TestPrincipal.class, "exact", new ExactPrincipalEvalPredicateFactory());
-        rpc.setOperator("exact");
-        rpc.setRequestedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
+        action.initialize();
+
+        doExtract(prc);
+
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
+        Assert.assertNotNull(ac.getAuthenticationResult());
+        Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(UsernamePrincipal.class).iterator()
+                .next().getName(), "PETER_THE_PRINCIPAL2");
+    }
+
+    @Test public void testAuthorizedCrypt() throws Exception {
+        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL3");
+        ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
+
+        final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
+        ac.setAttemptedFlow(authenticationFlows.get(0));
 
-        validator.setLoginConfigurations(Collections.singletonList(new Pair<String,Collection<Principal>>("ShibUserPassAuth",
-                Collections.<Principal>singletonList(new TestPrincipal("test1")))));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
         validator.initialize();
         
         action.initialize();
@@ -353,22 +276,17 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
         Assert.assertNotNull(ac.getAuthenticationResult());
         Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(UsernamePrincipal.class).iterator()
-                .next().getName(), "PETER_THE_PRINCIPAL");
-        Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(TestPrincipal.class).iterator()
-                .next().getName(), "test1");
+                .next().getName(), "PETER_THE_PRINCIPAL3");
     }
     
-    @Test public void testMultiConfigAuthorized() throws Exception {
+    @Test public void testAuthorizedAndKeep() throws Exception {
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
 
-        validator.setLoginConfigNames(Arrays.asList("ShibBadAuth", "ShibUserPassAuth"));
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
+        validator.setRemoveContextAfterValidation(false);
         validator.initialize();
         
         action.initialize();
@@ -378,23 +296,26 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
         
-        Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
+        Assert.assertNotNull(ac.getSubcontext(UsernamePasswordContext.class));
         Assert.assertNotNull(ac.getAuthenticationResult());
         Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(UsernamePrincipal.class).iterator()
                 .next().getName(), "PETER_THE_PRINCIPAL");
     }
-    
-    @Test public void testMatchAndAuthorized() throws Exception {
+
+    @Test public void testSupported() throws Exception {
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("username", "PETER_THE_PRINCIPAL");
         ((MockHttpServletRequest) action.getHttpServletRequest()).addParameter("password", "changeit");
 
         final AuthenticationContext ac = prc.getSubcontext(AuthenticationContext.class);
         ac.setAttemptedFlow(authenticationFlows.get(0));
+        
+        final RequestedPrincipalContext rpc = ac.getSubcontext(RequestedPrincipalContext.class, true);
+        rpc.getPrincipalEvalPredicateFactoryRegistry().register(
+                TestPrincipal.class, "exact", new ExactPrincipalEvalPredicateFactory());
+        rpc.setOperator("exact");
+        rpc.setRequestedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
 
-        validator.setLoginConfigType("JavaLoginConfig");
-        validator.setLoginConfigParameters(new URIParameter(URISupport.fileURIFromAbsolutePath(getCurrentDir()
-                + '/' + DATA_PATH + "jaas.config")));
-        validator.setMatchExpression(Pattern.compile(".+_THE_.+"));
+        validator.setSupportedPrincipals(Collections.<Principal>singletonList(new TestPrincipal("test1")));
         validator.initialize();
         
         action.initialize();
@@ -403,11 +324,13 @@ public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationC
 
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
         
+        Assert.assertNull(ac.getSubcontext(UsernamePasswordContext.class));
         Assert.assertNotNull(ac.getAuthenticationResult());
         Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(UsernamePrincipal.class).iterator()
                 .next().getName(), "PETER_THE_PRINCIPAL");
+        Assert.assertEquals(ac.getAuthenticationResult().getSubject().getPrincipals(TestPrincipal.class).iterator()
+                .next().getName(), "test1");
     }
     
     private void doExtract(ProfileRequestContext prc) throws Exception {
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/JAASCredentialValidatorTest.java
similarity index 99%
rename from idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
rename to idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/JAASCredentialValidatorTest.java
index 3757665..bcf7669 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstJAASTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/JAASCredentialValidatorTest.java
@@ -58,7 +58,7 @@ import com.unboundid.ldap.listener.InMemoryListenerConfig;
 import com.unboundid.ldap.sdk.LDAPException;
 
 /** Unit test for JAAS validation. */
-public class ValidateUsernamePasswordAgainstJAASTest extends BaseAuthenticationContextTest {
+public class JAASCredentialValidatorTest extends BaseAuthenticationContextTest {
 
     private static final String DATA_PATH = "src/test/resources/net/shibboleth/idp/authn/impl/";
     
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
similarity index 99%
rename from idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java
rename to idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
index 8175e77..adf8ada 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/ValidateUsernamePasswordAgainstLDAPTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/LDAPCredentialValidatorTest.java
@@ -64,7 +64,7 @@ import com.unboundid.ldap.listener.InMemoryListenerConfig;
 import com.unboundid.ldap.sdk.LDAPException;
 
 /** Unit test for LDAP credential validation. */
-public class ValidateUsernamePasswordAgainstLDAPTest extends BaseAuthenticationContextTest {
+public class LDAPCredentialValidatorTest extends BaseAuthenticationContextTest {
 
     private static final String DATA_PATH = "src/test/resources/net/shibboleth/idp/authn/impl/";
 
diff --git a/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/htpasswd.txt b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/htpasswd.txt
new file mode 100644
index 0000000..43d0606
--- /dev/null
+++ b/idp-authn-impl/src/test/resources/net/shibboleth/idp/authn/impl/htpasswd.txt
@@ -0,0 +1,3 @@
+PETER_THE_PRINCIPAL:$apr1$vBz5k7hO$RuB./7oGOpH05ga4aeb2f/
+PETER_THE_PRINCIPAL2:{SHA}BzE/DjIPIsv6Nc/CIFCOs/9FfH4=
+PETER_THE_PRINCIPAL3:fhGkPZphLLGwE
diff --git a/idp-conf/src/main/resources/conf/authn/password-authn-config.xml b/idp-conf/src/main/resources/conf/authn/password-authn-config.xml
index 3b8e3bb..bfde882 100644
--- a/idp-conf/src/main/resources/conf/authn/password-authn-config.xml
+++ b/idp-conf/src/main/resources/conf/authn/password-authn-config.xml
@@ -65,6 +65,7 @@
         <entry key="UnknownUsername">
             <list>
                 <value>NoCredentials</value>
+                <value>UnknownUsername</value>
                 <value>CLIENT_NOT_FOUND</value>
                 <value>Client not found</value>
                 <value>DN_RESOLUTION_FAILURE</value>
diff --git a/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml b/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
index d0dd086..00533d1 100644
--- a/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/password-authn-beans.xml
@@ -70,6 +70,11 @@
         p:removeContextAfterValidation="#{getObject('shibboleth.authn.Password.RemoveAfterValidation') ?: true}"
         p:matchExpression="#{getObject('shibboleth.authn.Password.matchExpression')}" />
 
+    <!-- New validator(s) that didn't exist in prior versions. -->
+    <bean id="shibboleth.HTPasswdCredentialValidator" parent="shibboleth.CredentialValidator" abstract="true"
+        class="net.shibboleth.idp.authn.impl.HTPasswdCredentialValidator"
+        p:id="htpasswd" />
+
     <!-- Alias the legacy names into "officially" supported parent bean names. -->
 
     <alias alias="shibboleth.JAASValidator" name="ValidateUsernamePasswordAgainstJAAS" />

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


More information about the commits mailing list