From 894f3cd2246b053c422a436916d5a38dff3dd244 Mon Sep 17 00:00:00 2001
From: Mike McKay <mfmckay@wustl.edu>
Date: Thu, 28 Jul 2016 11:40:15 -0500
Subject: [PATCH] XNAT-4387 Made auth methods fully customizable via plugins.
 If none are specified, it will default to db authentication, but if any are
 specified, only the specified ones will show up. I also added support for an
 order property to control the order of the methods in the dropdown, as well
 as the order in which they are checked when users attempt authentication. I
 see this plugin-based way of configuring providers remaining as an
 alternative to AdminUI configuration once that is implemented.

---
 .../xnat/security/XnatProviderManager.java    |  8 +-
 .../AliasTokenAuthenticationProvider.java     | 11 +++
 .../AuthenticationProviderAggregator.java     | 75 +++++++++++++------
 ...aseAuthenticationProviderConfigurator.java | 11 ++-
 ...dapAuthenticationProviderConfigurator.java |  3 +
 .../provider/XnatAuthenticationProvider.java  |  9 +++
 .../XnatDatabaseAuthenticationProvider.java   | 11 +++
 .../XnatLdapAuthenticationProvider.java       | 11 +++
 8 files changed, 112 insertions(+), 27 deletions(-)

diff --git a/src/main/java/org/nrg/xnat/security/XnatProviderManager.java b/src/main/java/org/nrg/xnat/security/XnatProviderManager.java
index 3ca88bb0..dcd35888 100644
--- a/src/main/java/org/nrg/xnat/security/XnatProviderManager.java
+++ b/src/main/java/org/nrg/xnat/security/XnatProviderManager.java
@@ -253,9 +253,11 @@ public class XnatProviderManager extends ProviderManager {
     private XnatAuthenticationProvider findAuthenticationProvider(XnatAuthenticationProviderMatcher matcher) {
         List<AuthenticationProvider> prov = getProviders();
         for (AuthenticationProvider ap : prov) {
-            XnatAuthenticationProvider xap = (XnatAuthenticationProvider) ap;
-            if (matcher.matches(xap)) {
-                return xap;
+            if(XnatAuthenticationProvider.class.isAssignableFrom(ap.getClass())) {
+                XnatAuthenticationProvider xap = (XnatAuthenticationProvider) ap;
+                if (matcher.matches(xap)) {
+                    return xap;
+                }
             }
         }
         return null;
diff --git a/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java
index e0de6d27..51840c2d 100644
--- a/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java
+++ b/src/main/java/org/nrg/xnat/security/alias/AliasTokenAuthenticationProvider.java
@@ -110,6 +110,16 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent
         return getName();
     }
 
+    @Override
+    public int getOrder() {
+        return _order;
+    }
+
+    @Override
+    public void setOrder(int order) {
+        _order = order;
+    }
+
     @Override
     protected void additionalAuthenticationChecks(final UserDetails userDetails, final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
         if (authentication.getCredentials() == null) {
@@ -173,4 +183,5 @@ public class AliasTokenAuthenticationProvider extends AbstractUserDetailsAuthent
 
     private final AliasTokenService   _aliasTokenService;
     private final XdatUserAuthService _userAuthService;
+    private int _order = -1;
 }
diff --git a/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java b/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java
index 6990368e..f7ac4ba6 100644
--- a/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java
+++ b/src/main/java/org/nrg/xnat/security/config/AuthenticationProviderAggregator.java
@@ -2,6 +2,7 @@ package org.nrg.xnat.security.config;
 
 import org.apache.commons.lang3.StringUtils;
 import org.nrg.framework.utilities.BasicXnatResourceLocator;
+import org.nrg.xnat.security.provider.XnatAuthenticationProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.io.Resource;
@@ -13,29 +14,33 @@ import java.util.*;
 public class AuthenticationProviderAggregator extends ArrayList<AuthenticationProvider> {
 
     public AuthenticationProviderAggregator(List<AuthenticationProvider> standaloneProviders, Map<String, AuthenticationProviderConfigurator> configurators) {
-        ArrayList<String> providerArray = new ArrayList<>();
-        String            dbName        = "Database";
-        String            dbId          = "localdb";
-        String            dbType        = "db";
-        providerArray.add(dbType);
-        HashMap<String, HashMap<String, String>> providerMap = new HashMap<>();
-        providerMap.put(dbType, new HashMap<String, String>());
-        providerMap.get(dbType).put("name", dbName);
-        providerMap.get(dbType).put("id", dbId);
-        providerMap.get(dbType).put("type", dbType);
+        ArrayList<HashMap<String, String>> providerList = new ArrayList<>();
 
         // Populate map of properties
         try {
             String               filenameEnd = "-provider.properties";
             final List<Resource> resources   = BasicXnatResourceLocator.getResources("classpath*:META-INF/xnat/auth/**/*" + filenameEnd);
-            for (final Resource resource : resources) {
-                String filename = resource.getFilename();
-                String id       = filename.substring(0, (filename.length() - filenameEnd.length()));
-                providerMap.put(id, new HashMap<String, String>());
-                providerArray.add(id);
-                final Properties provider = PropertiesLoaderUtils.loadProperties(resource);
-                for (Map.Entry<Object, Object> providerProperty : provider.entrySet()) {
-                    providerMap.get(id).put(providerProperty.getKey().toString(), providerProperty.getValue().toString());
+            if(resources==null || resources.isEmpty()){
+                String            dbName        = "Database";
+                String            dbId          = "localdb";
+                String            dbType        = "db";
+                HashMap<String, String> dbProv = new HashMap<String, String>();
+                dbProv.put("name", dbName);
+                dbProv.put("id", dbId);
+                dbProv.put("type", dbType);
+                providerList.add(dbProv);
+            }
+            else {
+                for (final Resource resource : resources) {
+                    String filename = resource.getFilename();
+                    String id = filename.substring(0, (filename.length() - filenameEnd.length()));
+                    HashMap<String, String> newProv = new HashMap<String, String>();
+
+                    final Properties provider = PropertiesLoaderUtils.loadProperties(resource);
+                    for (Map.Entry<Object, Object> providerProperty : provider.entrySet()) {
+                        newProv.put(providerProperty.getKey().toString(), providerProperty.getValue().toString());
+                    }
+                    providerList.add(newProv);
                 }
             }
         } catch (Exception e) {
@@ -43,10 +48,10 @@ public class AuthenticationProviderAggregator extends ArrayList<AuthenticationPr
         }
 
         // Create providers
-        for (String prov : providerArray) {
-            String name = providerMap.get(prov).get("name");
-            String id   = providerMap.get(prov).get("id");
-            String type = providerMap.get(prov).get("type");
+        for (HashMap<String, String> prov : providerList) {
+            String name = prov.get("name");
+            String id   = prov.get("id");
+            String type = prov.get("type");
 
             assert !StringUtils.isBlank(name) : "You must provide a name for all authentication provider configurations";
             assert !StringUtils.isBlank(id) : "You must provide an ID for all authentication provider configurations";
@@ -54,13 +59,37 @@ public class AuthenticationProviderAggregator extends ArrayList<AuthenticationPr
 
             if (configurators.containsKey(type)) {
                 AuthenticationProviderConfigurator configurator = configurators.get(type);
-                addAll(configurator.getAuthenticationProviders(id, name, providerMap.get(prov)));
+
+                addAll(configurator.getAuthenticationProviders(id, name, prov));
             }
         }
 
         if (standaloneProviders != null) {
             addAll(standaloneProviders);
         }
+
+        Collections.sort(this, new Comparator<AuthenticationProvider>(){
+            public int compare(AuthenticationProvider o1, AuthenticationProvider o2){
+                if(XnatAuthenticationProvider.class.isAssignableFrom(o1.getClass())){
+                    if(XnatAuthenticationProvider.class.isAssignableFrom(o2.getClass())){
+                        if(((XnatAuthenticationProvider)o1).getOrder() == ((XnatAuthenticationProvider)o2).getOrder())
+                            return 0;
+                        return ((XnatAuthenticationProvider)o1).getOrder() < ((XnatAuthenticationProvider)o2).getOrder() ? -1 : 1;
+                    }
+                    else{
+                        return 1;
+                    }
+                }
+                else{
+                    if(XnatAuthenticationProvider.class.isAssignableFrom(o2.getClass())){
+                        return -1;
+                    }
+                    else{
+                        return 0;
+                    }
+                }
+            }
+        });
     }
 
     private static final Logger _log = LoggerFactory.getLogger(AuthenticationProviderAggregator.class);
diff --git a/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java b/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java
index 0b306e52..f9fba0b3 100644
--- a/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java
+++ b/src/main/java/org/nrg/xnat/security/config/DatabaseAuthenticationProviderConfigurator.java
@@ -11,6 +11,7 @@
 package org.nrg.xnat.security.config;
 
 import org.nrg.xdat.preferences.SiteConfigPreferences;
+import org.nrg.xnat.security.provider.XnatAuthenticationProvider;
 import org.nrg.xnat.security.provider.XnatDatabaseAuthenticationProvider;
 import org.nrg.xnat.security.userdetailsservices.XnatDatabaseUserDetailsService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -51,7 +52,15 @@ public class DatabaseAuthenticationProviderConfigurator extends AbstractAuthenti
 
     @Override
     public List<AuthenticationProvider> getAuthenticationProviders(String id, String name, Map<String, String> properties) {
-        return getAuthenticationProviders(id, name);
+        List<AuthenticationProvider> provs = getAuthenticationProviders(id, name);
+        for(AuthenticationProvider prov : provs){
+            if(XnatAuthenticationProvider.class.isAssignableFrom(prov.getClass())){
+                if (properties.get("order") != null) {
+                    ((XnatAuthenticationProvider)prov).setOrder(Integer.parseInt(properties.get("order")));
+                }
+            }
+        }
+        return provs;
     }
 
     private final XnatDatabaseUserDetailsService _userDetailsService;
diff --git a/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java b/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java
index 851f87ef..1956761f 100644
--- a/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java
+++ b/src/main/java/org/nrg/xnat/security/config/LdapAuthenticationProviderConfigurator.java
@@ -49,6 +49,9 @@ public class LdapAuthenticationProviderConfigurator extends AbstractAuthenticati
             ldapAuthProvider.setUserDetailsContextMapper(new XnatLdapUserDetailsMapper(id, properties, _userAuthService, _preferences));
             ldapAuthProvider.setName(name);
             ldapAuthProvider.setProviderId(id);
+            if (properties.get("order") != null) {
+                ldapAuthProvider.setOrder(Integer.parseInt(properties.get("order")));
+            }
             return Arrays.asList(new AuthenticationProvider[] { ldapAuthProvider });
         } catch (Exception exception) {
             _log.error("Something went wrong when configuring the LDAP authentication provider", exception);
diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java
index 76535627..a23011a5 100644
--- a/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java
+++ b/src/main/java/org/nrg/xnat/security/provider/XnatAuthenticationProvider.java
@@ -40,4 +40,13 @@ public interface XnatAuthenticationProvider extends AuthenticationProvider {
      * @return The authentication method for this provider.
      */
     String getAuthMethod();
+
+    /**
+     * Indicates the order associated with this provider. This is used to determine the order in which the providers
+     * show up in the login dropdown and the order in which they are checked when a login is attempted.
+     * @return The order for this provider.
+     */
+    int getOrder();
+
+    void setOrder(int order);
 }
diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java
index 8dac7b24..3ad7b36a 100644
--- a/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java
+++ b/src/main/java/org/nrg/xnat/security/provider/XnatDatabaseAuthenticationProvider.java
@@ -74,6 +74,16 @@ public class XnatDatabaseAuthenticationProvider extends DaoAuthenticationProvide
         return XdatUserAuthService.LOCALDB;
     }
 
+    @Override
+    public int getOrder() {
+        return _order;
+    }
+
+    @Override
+    public void setOrder(int order) {
+        _order = order;
+    }
+
     @Override
     protected void additionalAuthenticationChecks(final UserDetails userDetails, final UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
         if (!UserI.class.isAssignableFrom(userDetails.getClass())) {
@@ -143,4 +153,5 @@ public class XnatDatabaseAuthenticationProvider extends DaoAuthenticationProvide
     }
 
     private final boolean _requireEmailVerification;
+    private int _order = -1;
 }
diff --git a/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java b/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java
index c57c49ed..20fa4dc3 100644
--- a/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java
+++ b/src/main/java/org/nrg/xnat/security/provider/XnatLdapAuthenticationProvider.java
@@ -94,6 +94,16 @@ public class XnatLdapAuthenticationProvider extends LdapAuthenticationProvider i
         return XdatUserAuthService.LDAP;
     }
 
+	@Override
+	public int getOrder() {
+		return _order;
+	}
+
+	@Override
+	public void setOrder(int order) {
+		_order = order;
+	}
+
     /**
      * Indicates whether the provider should be visible to and selectable by users. <b>false</b> usually indicates an
      * internal authentication provider, e.g. token authentication.
@@ -109,4 +119,5 @@ public class XnatLdapAuthenticationProvider extends LdapAuthenticationProvider i
 
     private String _displayName = "";
     private String _providerId = "";
+	private int _order = -1;
 }
-- 
GitLab