You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2016/07/26 14:45:14 UTC

mina-sshd git commit: [SSHD-680] Provide default implementations for ClientAuthenticationManager#getSetUserAuthFactories overloading with strings

Repository: mina-sshd
Updated Branches:
  refs/heads/master bdbdc9c6a -> 59cad4512


[SSHD-680] Provide default implementations for ClientAuthenticationManager#getSetUserAuthFactories overloading with strings


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/59cad451
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/59cad451
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/59cad451

Branch: refs/heads/master
Commit: 59cad45129c5d9323f687ca031e5bc7aff30fd9e
Parents: bdbdc9c
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Tue Jul 26 17:44:23 2016 +0300
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Tue Jul 26 17:44:23 2016 +0300

----------------------------------------------------------------------
 .../client/ClientAuthenticationManager.java     |  29 ++++
 .../client/auth/BuiltinUserAuthFactories.java   | 140 +++++++++++++++++++
 .../hostbased/UserAuthHostBasedFactory.java     |  50 +++++++
 .../sshd/common/cipher/BuiltinCiphers.java      |   3 +-
 .../client/ClientAuthenticationManagerTest.java | 100 +++++++++++++
 .../auth/BuiltinUserAuthFactoriesTest.java      | 115 +++++++++++++++
 6 files changed, 435 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
index a34b8e4..afe1f92 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientAuthenticationManager.java
@@ -20,15 +20,22 @@
 package org.apache.sshd.client;
 
 import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
+import org.apache.sshd.client.auth.BuiltinUserAuthFactories;
 import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
 import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
 import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.ValidateUtils;
 
 /**
  * Holds information required for the client to perform authentication with the server
@@ -123,5 +130,27 @@ public interface ClientAuthenticationManager extends KeyPairProviderHolder {
      * {@code null}/empty
      */
     List<NamedFactory<UserAuth>> getUserAuthFactories();
+    default String getUserAuthFactoriesNameList() {
+        return NamedResource.Utils.getNames(getUserAuthFactories());
+    }
+    default List<String> getUserAuthFactoriesNames() {
+        return NamedResource.Utils.getNameList(getUserAuthFactories());
+    }
+
     void setUserAuthFactories(List<NamedFactory<UserAuth>> userAuthFactories);
+    default void setUserAuthFactoriesNameList(String names) {
+        setUserAuthFactoriesNames(GenericUtils.split(names, ','));
+    }
+    default void setUserAuthFactoriesNames(String ... names) {
+        setUserAuthFactoriesNames(GenericUtils.isEmpty((Object[]) names) ? Collections.<String>emptyList() : Arrays.asList(names));
+    }
+    default void setUserAuthFactoriesNames(Collection<String> names) {
+        BuiltinUserAuthFactories.ParseResult result = BuiltinUserAuthFactories.parseFactoriesList(names);
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        List<NamedFactory<UserAuth>> factories =
+                (List) ValidateUtils.checkNotNullAndNotEmpty(result.getParsedFactories(), "No supported cipher factories: %s", names);
+        Collection<String> unsupported = result.getUnsupportedFactories();
+        ValidateUtils.checkTrue(GenericUtils.isEmpty(unsupported), "Unsupported cipher factories found: %s", unsupported);
+        setUserAuthFactories(factories);
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/main/java/org/apache/sshd/client/auth/BuiltinUserAuthFactories.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/BuiltinUserAuthFactories.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/BuiltinUserAuthFactories.java
new file mode 100644
index 0000000..27ecb2f
--- /dev/null
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/BuiltinUserAuthFactories.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.sshd.client.auth;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.sshd.client.auth.hostbased.UserAuthHostBasedFactory;
+import org.apache.sshd.client.auth.keyboard.UserAuthKeyboardInteractiveFactory;
+import org.apache.sshd.client.auth.password.UserAuthPasswordFactory;
+import org.apache.sshd.client.auth.pubkey.UserAuthPublicKeyFactory;
+import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.config.NamedFactoriesListParseResult;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * Provides a centralized location for the default built-in authentication factories
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public enum BuiltinUserAuthFactories implements NamedFactory<UserAuthFactory> {
+    PASSWORD(UserAuthPasswordFactory.INSTANCE),
+    PUBLICKEY(UserAuthPublicKeyFactory.INSTANCE),
+    KBINTERACTIVE(UserAuthKeyboardInteractiveFactory.INSTANCE),
+    HOSTBASED(UserAuthHostBasedFactory.INSTANCE);
+
+    public static final Set<BuiltinUserAuthFactories> VALUES =
+            Collections.unmodifiableSet(EnumSet.allOf(BuiltinUserAuthFactories.class));
+
+    private final UserAuthFactory factory;
+
+    BuiltinUserAuthFactories(UserAuthFactory factory) {
+        this.factory = Objects.requireNonNull(factory, "No delegate factory instance");
+    }
+
+    @Override
+    public UserAuthFactory create() {
+        return factory;
+    }
+
+    @Override
+    public String getName() {
+        return factory.getName();
+    }
+
+    /**
+     * @param name The factory name (case <U>insensitive</U>) - ignored if {@code null}/empty
+     * @return The matching factory instance - {@code null} if no match found
+     */
+    public static UserAuthFactory fromFactoryName(String name) {
+        Factory<UserAuthFactory> factory = NamedResource.Utils.findByName(name, String.CASE_INSENSITIVE_ORDER, VALUES);
+        if (factory == null) {
+            return null;
+        }
+
+        return factory.create();
+    }
+
+    /**
+     * @param factories A comma-separated list of factories' names - ignored if {@code null}/empty
+     * @return A {@link ParseResult} containing the successfully parsed
+     * factories and the unknown ones. <B>Note:</B> it is up to caller to
+     * ensure that the lists do not contain duplicates
+     */
+    public static ParseResult parseFactoriesList(String factories) {
+        return parseFactoriesList(GenericUtils.split(factories, ','));
+    }
+
+    public static ParseResult parseFactoriesList(String... factories) {
+        return parseFactoriesList(GenericUtils.isEmpty((Object[]) factories) ? Collections.<String>emptyList() : Arrays.asList(factories));
+    }
+
+    public static ParseResult parseFactoriesList(Collection<String> factories) {
+        if (GenericUtils.isEmpty(factories)) {
+            return ParseResult.EMPTY;
+        }
+
+        List<UserAuthFactory> resolved = new ArrayList<>(factories.size());
+        List<String> unknown = Collections.emptyList();
+        for (String name : factories) {
+            UserAuthFactory c = resolveFactory(name);
+            if (c != null) {
+                resolved.add(c);
+            } else {
+                // replace the (unmodifiable) empty list with a real one
+                if (unknown.isEmpty()) {
+                    unknown = new ArrayList<>();
+                }
+                unknown.add(name);
+            }
+        }
+
+        return new ParseResult(resolved, unknown);
+    }
+
+    public static UserAuthFactory resolveFactory(String name) {
+        if (GenericUtils.isEmpty(name)) {
+            return null;
+        }
+
+        return fromFactoryName(name);
+    }
+
+    /**
+     * Holds the result of {@link BuiltinUserAuthFactories#parseFactoriesList(String)}
+     *
+     * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+     */
+    public static class ParseResult extends NamedFactoriesListParseResult<UserAuth, UserAuthFactory> {
+        public static final ParseResult EMPTY = new ParseResult(Collections.<UserAuthFactory>emptyList(), Collections.<String>emptyList());
+
+        public ParseResult(List<UserAuthFactory> parsed, List<String> unsupported) {
+            super(parsed, unsupported);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBasedFactory.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBasedFactory.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBasedFactory.java
index 8ead888..1329d5a 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBasedFactory.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/hostbased/UserAuthHostBasedFactory.java
@@ -25,12 +25,62 @@ import org.apache.sshd.client.auth.AbstractUserAuthFactory;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.signature.Signature;
 import org.apache.sshd.common.signature.SignatureFactoriesManager;
+import org.apache.sshd.common.util.GenericUtils;
 
 /**
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public class UserAuthHostBasedFactory extends AbstractUserAuthFactory implements SignatureFactoriesManager {
     public static final String NAME = HOST_BASED;
+    public static final UserAuthHostBasedFactory INSTANCE = new UserAuthHostBasedFactory() {
+        @Override
+        public List<NamedFactory<Signature>> getSignatureFactories() {
+            return null;
+        }
+
+        @Override
+        public void setSignatureFactories(List<NamedFactory<Signature>> factories) {
+            if (!GenericUtils.isEmpty(factories)) {
+                throw new UnsupportedOperationException("Not allowed to change default instance signature factories");
+            }
+        }
+
+        @Override
+        public HostKeyIdentityProvider getClientHostKeys() {
+            return null;
+        }
+
+        @Override
+        public void setClientHostKeys(HostKeyIdentityProvider clientHostKeys) {
+            if (clientHostKeys != null) {
+                throw new UnsupportedOperationException("Not allowed to change default instance client host keys");
+            }
+        }
+
+        @Override
+        public String getClientUsername() {
+            return null;
+        }
+
+        @Override
+        public void setClientUsername(String clientUsername) {
+            if (!GenericUtils.isEmpty(clientUsername)) {
+                throw new UnsupportedOperationException("Not allowed to change default instance client username");
+            }
+        }
+
+        @Override
+        public String getClientHostname() {
+            return null;
+        }
+
+        @Override
+        public void setClientHostname(String clientHostname) {
+            if (!GenericUtils.isEmpty(clientHostname)) {
+                throw new UnsupportedOperationException("Not allowed to change default instance client hostname");
+            }
+        }
+    };
 
     private List<NamedFactory<Signature>> factories;
     private HostKeyIdentityProvider clientHostKeys;

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
index 3b236bd..9300b59 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/BuiltinCiphers.java
@@ -242,8 +242,7 @@ public enum BuiltinCiphers implements CipherFactory {
     }
 
     /**
-     * @param ciphers A comma-separated list of ciphers' names - ignored
-     *                if {@code null}/empty
+     * @param ciphers A comma-separated list of ciphers' names - ignored if {@code null}/empty
      * @return A {@link ParseResult} containing the successfully parsed
      * factories and the unknown ones. <B>Note:</B> it is up to caller to
      * ensure that the lists do not contain duplicates

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
index 56f7ebd..61dc4a2 100644
--- a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java
@@ -24,13 +24,20 @@ import java.lang.reflect.Method;
 import java.security.KeyPair;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.sshd.client.auth.AuthenticationIdentitiesProvider;
+import org.apache.sshd.client.auth.BuiltinUserAuthFactories;
+import org.apache.sshd.client.auth.UserAuth;
 import org.apache.sshd.client.auth.keyboard.UserInteraction;
 import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
 import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
 import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.client.session.ClientSessionImpl;
 import org.apache.sshd.common.Factory;
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.NamedResource;
 import org.apache.sshd.common.channel.Channel;
 import org.apache.sshd.common.channel.ChannelListener;
 import org.apache.sshd.common.forward.DefaultTcpipForwarderFactory;
@@ -59,6 +66,99 @@ public class ClientAuthenticationManagerTest extends BaseTestSupport {
     }
 
     @Test
+    public void testDefaultUserAuthFactoriesMethods() {
+        AtomicReference<List<NamedFactory<UserAuth>>> factoriesHolder = new AtomicReference<>();
+        @SuppressWarnings({"checkstyle:anoninnerlength", "checkstyle:methodlength"})
+        ClientAuthenticationManager manager = new ClientAuthenticationManager() {
+            @Override
+            public List<NamedFactory<UserAuth>> getUserAuthFactories() {
+                return factoriesHolder.get();
+            }
+
+            @Override
+            public void setUserAuthFactories(List<NamedFactory<UserAuth>> userAuthFactories) {
+                assertNull("Unexpected multiple invocation", factoriesHolder.getAndSet(userAuthFactories));
+            }
+
+            @Override
+            public KeyPairProvider getKeyPairProvider() {
+                return null;
+            }
+
+            @Override
+            public void setKeyPairProvider(KeyPairProvider keyPairProvider) {
+                throw new UnsupportedOperationException("setKeyPairProvider(" + keyPairProvider + ")");
+            }
+
+            @Override
+            public UserInteraction getUserInteraction() {
+                return null;
+            }
+
+            @Override
+            public void setUserInteraction(UserInteraction userInteraction) {
+                throw new UnsupportedOperationException("setUserInteraction(" + userInteraction + ")");
+            }
+
+            @Override
+            public ServerKeyVerifier getServerKeyVerifier() {
+                return null;
+            }
+
+            @Override
+            public void setServerKeyVerifier(ServerKeyVerifier serverKeyVerifier) {
+                throw new UnsupportedOperationException("setServerKeyVerifier(" + serverKeyVerifier + ")");
+            }
+
+            @Override
+            public PasswordIdentityProvider getPasswordIdentityProvider() {
+                return null;
+            }
+
+            @Override
+            public void setPasswordIdentityProvider(PasswordIdentityProvider provider) {
+                throw new UnsupportedOperationException("setPasswordIdentityProvider(" + provider + ")");
+            }
+
+            @Override
+            public AuthenticationIdentitiesProvider getRegisteredIdentities() {
+                return null;
+            }
+
+            @Override
+            public void addPublicKeyIdentity(KeyPair key) {
+                throw new UnsupportedOperationException("addPublicKeyIdentity(" + key + ")");
+            }
+
+            @Override
+            public KeyPair removePublicKeyIdentity(KeyPair kp) {
+                throw new UnsupportedOperationException("removePublicKeyIdentity(" + kp + ")");
+            }
+
+            @Override
+            public void addPasswordIdentity(String password) {
+                throw new UnsupportedOperationException("addPasswordIdentity(" + password + ")");
+            }
+
+            @Override
+            public String removePasswordIdentity(String password) {
+                throw new UnsupportedOperationException("removePasswordIdentity(" + password + ")");
+            }
+        };
+        assertEquals("Mismatched initial factories list", "", manager.getUserAuthFactoriesNameList());
+
+        String expected = NamedResource.Utils.getNames(BuiltinUserAuthFactories.VALUES);
+        manager.setUserAuthFactoriesNameList(expected);
+        assertEquals("Mismatched updated factories names", expected, manager.getUserAuthFactoriesNameList());
+
+        List<NamedFactory<UserAuth>> factories = factoriesHolder.get();
+        assertEquals("Mismatched factories count", BuiltinUserAuthFactories.VALUES.size(), GenericUtils.size(factories));
+        for (BuiltinUserAuthFactories f : BuiltinUserAuthFactories.VALUES) {
+            assertTrue("Missing factory=" + f.name(), factories.contains(f.create()));
+        }
+    }
+
+    @Test
     public void testAddRemoveClientSessionIdentities() throws Exception {
         try (ClientSession session = createMockClientSession()) {
             testClientAuthenticationManager(session);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/59cad451/sshd-core/src/test/java/org/apache/sshd/client/auth/BuiltinUserAuthFactoriesTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/client/auth/BuiltinUserAuthFactoriesTest.java b/sshd-core/src/test/java/org/apache/sshd/client/auth/BuiltinUserAuthFactoriesTest.java
new file mode 100644
index 0000000..ee81a0b
--- /dev/null
+++ b/sshd-core/src/test/java/org/apache/sshd/client/auth/BuiltinUserAuthFactoriesTest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.sshd.client.auth;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.TreeSet;
+
+import org.apache.sshd.client.auth.BuiltinUserAuthFactories.ParseResult;
+import org.apache.sshd.common.auth.UserAuthMethodFactory;
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)   // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+public class BuiltinUserAuthFactoriesTest extends BaseTestSupport {
+    private final BuiltinUserAuthFactories factory;
+
+    public BuiltinUserAuthFactoriesTest(BuiltinUserAuthFactories factory) {
+        this.factory = factory;
+    }
+
+    @Parameters(name = "Factory={0}")
+    public static Collection<Object[]> parameters() {
+        return parameterize(BuiltinUserAuthFactories.VALUES);
+    }
+
+    @BeforeClass
+    public static void testAllConstantsCovered() throws Exception {
+        Field[] fields = UserAuthMethodFactory.class.getDeclaredFields();
+        Collection<String> factories = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+        for (Field f : fields) {
+            if (f.getType() != String.class) {
+                continue;
+            }
+
+            int mods = f.getModifiers();
+            if ((!Modifier.isStatic(mods)) || (!Modifier.isFinal(mods)) || (!Modifier.isPublic(mods))) {
+                continue;
+            }
+
+            String name = Objects.toString(f.get(null), null);
+            UserAuthFactory factory = BuiltinUserAuthFactories.fromFactoryName(name);
+            if (factory == null) {
+                continue;
+            }
+
+            assertTrue("Duplicate factory name constant: " + name, factories.add(name));
+        }
+
+        assertEquals("Mismatched factories names count: " + factories, factories.size(), BuiltinUserAuthFactories.VALUES.size());
+    }
+
+    @Test
+    public void testSingletonFactoryInstance() {
+        UserAuthFactory expected = factory.create();
+        for (int index = 1; index <= Byte.SIZE; index++) {
+            assertSame("Mismatched factory instance at invocation #" + index, expected, factory.create());
+        }
+    }
+
+    @Test
+    public void testFromFactoryName() {
+        String name = factory.getName();
+        UserAuthFactory expected = factory.create();
+        for (int index = 1, count = name.length(); index <= count; index++) {
+            UserAuthFactory actual = BuiltinUserAuthFactories.fromFactoryName(name);
+            assertSame("Mismatched factory instance for name=" + name, expected, actual);
+            name = shuffleCase(name);   // prepare for next iteration
+        }
+    }
+
+    @Test
+    public void testParseResult() {
+        ParseResult result = BuiltinUserAuthFactories.parseFactoriesList(factory.getName());
+        assertNotNull("No parse result", result);
+
+        List<UserAuthFactory> parsed = result.getParsedFactories();
+        assertEquals("Mismatched parsed count", 1, GenericUtils.size(parsed));
+        assertSame("Mismatched parsed factory instance", factory.create(), parsed.get(0));
+
+        Collection<String> unsupported = result.getUnsupportedFactories();
+        assertTrue("Unexpected unsupported values: " + unsupported, GenericUtils.isEmpty(unsupported));
+    }
+}