Repository: incubator-eagle
Updated Branches:
  refs/heads/master 18f74d448 -> ce8e1c508


[MINOR] add settings for case: ldap authentication over ssl

To make it fit for ldap authentication over ssl protocol, add:
  1. one config-attribute to indicate certificate absolute path.
  2. checking code for verifying certificate's existence.
  3. add unit test cases to cover the logic branches.

Author: anyway1021 <m...@apache.org>

Closes #661 from anyway1021/ldap-auth-improve.


Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/ce8e1c50
Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/ce8e1c50
Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/ce8e1c50

Branch: refs/heads/master
Commit: ce8e1c5080c0f9a8fae9010cbd29dc3c138f13d8
Parents: 18f74d4
Author: anyway1021 <m...@apache.org>
Authored: Thu Nov 17 13:18:27 2016 +0800
Committer: Zhao, Qingwen <qingwz...@apache.org>
Committed: Thu Nov 17 13:18:27 2016 +0800

----------------------------------------------------------------------
 eagle-server-assembly/src/main/conf/server.yml  |  57 +++++++++-
 .../authenticator/LdapBasicAuthenticator.java   |  20 ++++
 .../authentication/config/LdapSettings.java     |  12 ++
 .../src/main/resources/configuration.yml        |   4 +
 .../LdapBasicAuthenticatorTest.java             | 110 ++++++++++++++++---
 .../src/test/resources/configuration.yml        |   4 +
 6 files changed, 192 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server-assembly/src/main/conf/server.yml
----------------------------------------------------------------------
diff --git a/eagle-server-assembly/src/main/conf/server.yml 
b/eagle-server-assembly/src/main/conf/server.yml
index 5ea8b31..501d941 100644
--- a/eagle-server-assembly/src/main/conf/server.yml
+++ b/eagle-server-assembly/src/main/conf/server.yml
@@ -42,4 +42,59 @@ logging:
       archive: true
       archivedLogFilenamePattern: log/eagle-server-%d.log
       archivedFileCount: 5
-      timeZone: UTC
\ No newline at end of file
+      timeZone: UTC
+
+# ---------------------------------------------
+# Eagle Authentication Configuration
+# ---------------------------------------------
+auth:
+  # indicating if authentication is enabled, true for enabled, false for 
disabled
+  enabled: false
+
+  # indicating authentication mode, "simple" or "ldap"
+  mode: simple
+
+  # indicating whether to use cache: cache is usually used for authentications 
that may
+  # not handle high throughput (an RDBMS or LDAP server, for example)
+  caching: false
+
+  # indicating the cache policy, containing maximumSize and expireAfterWrite, 
e.g. maximumSize=10000, expireAfterWrite=10m
+  cachePolicy: maximumSize=10000, expireAfterWrite=1m
+
+  # indicating whether authorization is needed
+  authorization: false
+
+  # indicating whether @Auth annotation on parameters is needed
+  annotated: true
+
+  # for basic authentication, effective only when auth.mode=simple
+  simple:
+    # username for basic authentication, effective only when auth.mode=simple
+    username: admin
+    # password for basic authentication, effective only when auth.mode=simple
+    password: secret
+
+  # for ldap authentication, effective only when auth.mode=ldap
+  ldap:
+    # url providing ldap service. By convention, the port for typical ldap 
service is 389, and ldap service over ssl
+    # uses port 636 with protocol "ldaps", which requires certificates 
pre-installed.
+    providerUrl: ldap://server.address.or.domain:port
+
+    # template string containing ${USERNAME} placeholder. This is designed for 
some orgs who don't use plain usernames
+    # to authenticate, e.g. they may use its members' email address as the 
username: ${USERNAME}@some.org. When username
+    # is supposed to be recognized originally, just configure this parameter 
as ${USERNAME}
+    principalTemplate: ${USERNAME}@maybe.email.suffix
+
+    # string of strategy used by ldap service. "simple" is usually supported 
in most circumstances, we can use it by
+    # default or leave it a blank string.
+    strategy: simple
+
+    # the absolute path of ssl certificate file. This attribute is required 
conditional only when the auth -> mode is set
+    # as "ldap" and providerUrl starting with "ldaps://".
+    certificateAbsolutePath: /certificate/absolute/path
+
+    # timeout expression for connecting to ldap service endpoint
+    connectingTimeout: 500ms
+
+    # timeout expression for reading from ldap service
+    readingTimeout: 500ms

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java
----------------------------------------------------------------------
diff --git 
a/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java
 
b/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java
index 14652c3..c67dea8 100644
--- 
a/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java
+++ 
b/eagle-server/src/main/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticator.java
@@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.naming.Context;
 import javax.naming.directory.InitialDirContext;
+import java.io.File;
 import java.util.Hashtable;
 
 public class LdapBasicAuthenticator implements Authenticator<BasicCredentials, 
User> {
@@ -34,6 +35,10 @@ public class LdapBasicAuthenticator implements 
Authenticator<BasicCredentials, U
     private static final String LDAP_LDAP_CTX_FACTORY_NAME = 
"com.sun.jndi.ldap.LdapCtxFactory";
     private static final String LDAP_CONNECT_TIMEOUT_KEY = 
"com.sun.jndi.ldap.connect.timeout";
     private static final String LDAP_READ_TIMEOUT_KEY = 
"com.sun.jndi.ldap.read.timeout";
+    private static final String SYS_PROP_SSL_KEY_STORE = 
"javax.net.ssl.keyStore";
+    private static final String SYS_PROP_SSL_TRUST_STORE = 
"javax.net.ssl.trustStore";
+    private static final String LDAPS_URL_PREFIX = "ldaps://";
+    private static final String SSL_PROTOCOL_VALUE = "ssl";
     private LdapSettings settings = null;
 
     public LdapBasicAuthenticator(LdapSettings settings) {
@@ -70,6 +75,21 @@ public class LdapBasicAuthenticator implements 
Authenticator<BasicCredentials, U
             env.put(Context.SECURITY_AUTHENTICATION, strategy);
         }
 
+        if (providerUrl.toLowerCase().startsWith(LDAPS_URL_PREFIX)) { // using 
ldap over ssl to authenticate
+            env.put(Context.SECURITY_PROTOCOL, SSL_PROTOCOL_VALUE);
+
+            String certificateAbsolutePath = 
settings.getCertificateAbsolutePath();
+            if (certificateAbsolutePath == null || 
"".equals(certificateAbsolutePath)) {
+                throw new RuntimeException("The attribute 
'certificateAbsolutePath' must be set when using ldap over ssl to 
authenticate.");
+            }
+            if (!new File(certificateAbsolutePath).exists()) {
+                throw new RuntimeException(String.format("The file specified 
not existing: %s", certificateAbsolutePath));
+            }
+
+            System.setProperty(SYS_PROP_SSL_KEY_STORE, 
certificateAbsolutePath);
+            System.setProperty(SYS_PROP_SSL_TRUST_STORE, 
certificateAbsolutePath);
+        }
+
         env.put(Context.SECURITY_PRINCIPAL, 
comprisePrincipal(sanitizedUsername));
         env.put(Context.SECURITY_CREDENTIALS, password);
         return env;

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java
----------------------------------------------------------------------
diff --git 
a/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java
 
b/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java
index 6bb3303..9297e7e 100644
--- 
a/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java
+++ 
b/eagle-server/src/main/java/org/apache/eagle/server/authentication/config/LdapSettings.java
@@ -24,6 +24,7 @@ public class LdapSettings {
     private String providerUrl = "";
     private String strategy = "";
     private String principalTemplate = "";
+    private String certificateAbsolutePath = "";
     private Duration connectingTimeout = Duration.parse("500ms");
     private Duration readingTimeout = Duration.parse("500ms");
 
@@ -81,4 +82,15 @@ public class LdapSettings {
         this.readingTimeout = readingTimeout;
         return this;
     }
+
+    @JsonProperty
+    public String getCertificateAbsolutePath() {
+        return certificateAbsolutePath;
+    }
+
+    @JsonProperty
+    public LdapSettings setCertificateAbsolutePath(String 
certificateAbsolutePath) {
+        this.certificateAbsolutePath = certificateAbsolutePath;
+        return this;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/main/resources/configuration.yml
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/resources/configuration.yml 
b/eagle-server/src/main/resources/configuration.yml
index 8d388b0..eabb8a1 100644
--- a/eagle-server/src/main/resources/configuration.yml
+++ b/eagle-server/src/main/resources/configuration.yml
@@ -66,6 +66,10 @@ auth:
     # default or leave it a blank string.
     strategy: simple
 
+    # the absolute path of ssl certificate file. This attribute is required 
conditional only when the auth -> mode is set
+    # as "ldap" and providerUrl starting with "ldaps://".
+    certificateAbsolutePath: /certificate/absolute/path
+
     # timeout expression for connecting to ldap service endpoint
     connectingTimeout: 500ms
 

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java
----------------------------------------------------------------------
diff --git 
a/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java
 
b/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java
index 492521f..8700a75 100644
--- 
a/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java
+++ 
b/eagle-server/src/test/java/org/apache/eagle/server/authentication/authenticator/LdapBasicAuthenticatorTest.java
@@ -30,29 +30,38 @@ public class LdapBasicAuthenticatorTest {
     private static final String USERNAME_SUFFIX = "@some.emailbox.suffix";
     private static final String USERNAME_TEMPLATE = "${USERNAME}" + 
USERNAME_SUFFIX;
     private static final String LDAP_SERVICE_PROVIDER_URL = 
"ldap://some.address:port";;
+    private static final String LDAP_SERVICE_PROVIDER_SSL_URL = 
"ldaps://some.address:port";
     private static final String STRATEGY_SIMPLE = "customized";
     private static final String CONNECTING_TIMEOUT_VALUE = "500ms";
     private static final String READING_TIMEOUT_VALUE = "800ms";
     private static final String LDAP_CTX_FACTORY_NAME = 
"com.sun.jndi.ldap.LdapCtxFactory";
     private static final String LDAP_CONNECT_TIMEOUT_KEY = 
"com.sun.jndi.ldap.connect.timeout";
     private static final String LDAP_READ_TIMEOUT_KEY = 
"com.sun.jndi.ldap.read.timeout";
-    private static final LdapBasicAuthenticator AUTHENTICATOR_FOR_UTIL_METHODS 
= new LdapBasicAuthenticator(
-            new LdapSettings()
-                    .setProviderUrl(LDAP_SERVICE_PROVIDER_URL)
-                    .setPrincipalTemplate(USERNAME_TEMPLATE)
+    private static final String SYS_PROP_SSL_KEY_STORE = 
"javax.net.ssl.keyStore";
+    private static final String SYS_PROP_SSL_TRUST_STORE = 
"javax.net.ssl.trustStore";
+    private static final String EXISTING_MOCK_FILE_PATH = 
String.format("%s/pom.xml", System.getProperty("user.dir"));
+    private static final LdapBasicAuthenticator 
AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL = new LdapBasicAuthenticator(
+            getNonSSLPreSettings().setPrincipalTemplate(USERNAME_TEMPLATE)
                     .setStrategy(STRATEGY_SIMPLE)
                     
.setConnectingTimeout(Duration.parse(CONNECTING_TIMEOUT_VALUE))
                     .setReadingTimeout(Duration.parse(READING_TIMEOUT_VALUE))
     );
+    private static final LdapBasicAuthenticator 
AUTHENTICATOR_FOR_UTIL_METHODS_WITH_SSL = new LdapBasicAuthenticator(
+            getSSLPreSettings().setPrincipalTemplate(USERNAME_TEMPLATE)
+                    .setStrategy(STRATEGY_SIMPLE)
+                    .setCertificateAbsolutePath(EXISTING_MOCK_FILE_PATH)
+                    
.setConnectingTimeout(Duration.parse(CONNECTING_TIMEOUT_VALUE))
+                    .setReadingTimeout(Duration.parse(READING_TIMEOUT_VALUE))
+    );
 
     @Test
     public void testSanitizeUsername() {
         String correctUsername = "userNAME_123.45Z";
-        String sanitized = 
AUTHENTICATOR_FOR_UTIL_METHODS.sanitizeUsername(correctUsername);
+        String sanitized = 
AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.sanitizeUsername(correctUsername);
         Assert.assertEquals(correctUsername, sanitized);
 
         String incorrectUsername = 
"userNAME-~!@#$%^&777*()_+-=`[]\\{}|;':\",./<>?ä½ ";
-        sanitized = 
AUTHENTICATOR_FOR_UTIL_METHODS.sanitizeUsername(incorrectUsername);
+        sanitized = 
AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.sanitizeUsername(incorrectUsername);
         System.out.println(sanitized);
         Assert.assertEquals("userNAME777_.", sanitized);
     }
@@ -60,27 +69,100 @@ public class LdapBasicAuthenticatorTest {
     @Test
     public void testComprisePrincipal() {
         String username = "my.userNAME_123";
-        String principal = 
AUTHENTICATOR_FOR_UTIL_METHODS.comprisePrincipal(username);
+        String principal = 
AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.comprisePrincipal(username);
         Assert.assertEquals(username+USERNAME_SUFFIX, principal);
     }
 
     @Test
-    public void testGetContextEnvironment() {
+    public void testGetContextEnvironmentNormal() {
         String username = "username";
-        String secret_phrase = "secret-phrase";
-        Hashtable<String, String> env = 
AUTHENTICATOR_FOR_UTIL_METHODS.getContextEnvironment(username, secret_phrase);
+        String secretPhrase = "secret-phrase";
+        Hashtable<String, String> env = 
AUTHENTICATOR_FOR_UTIL_METHODS_WITHOUT_SSL.getContextEnvironment(username, 
secretPhrase);
 
         Assert.assertEquals("unexpected ldap context factory name", 
LDAP_CTX_FACTORY_NAME, env.get(Context.INITIAL_CONTEXT_FACTORY));
-        Assert.assertEquals("unexpected ldap serivce provider url", 
LDAP_SERVICE_PROVIDER_URL, env.get(Context.PROVIDER_URL));
+        Assert.assertEquals("unexpected ldap service provider url", 
LDAP_SERVICE_PROVIDER_URL, env.get(Context.PROVIDER_URL));
         Assert.assertEquals("unexpected connecting timeout value", 
String.valueOf(Duration.parse(CONNECTING_TIMEOUT_VALUE).toMilliseconds()), 
env.get(LDAP_CONNECT_TIMEOUT_KEY));
         Assert.assertEquals("unexpected reading timeout value", 
String.valueOf(Duration.parse(READING_TIMEOUT_VALUE).toMilliseconds()), 
env.get(LDAP_READ_TIMEOUT_KEY));
         Assert.assertEquals("unexpected username", username+USERNAME_SUFFIX, 
env.get(Context.SECURITY_PRINCIPAL));
-        Assert.assertEquals("unexpected secret credentials", secret_phrase, 
env.get(Context.SECURITY_CREDENTIALS));
+        Assert.assertEquals("unexpected secret credentials", secretPhrase, 
env.get(Context.SECURITY_CREDENTIALS));
         Assert.assertEquals("unexpected strategy", STRATEGY_SIMPLE, 
env.get(Context.SECURITY_AUTHENTICATION));
+    }
+
+    @Test
+    public void testGetContextEnvironmentBlankStrategy() {
+        String username = "username";
+        String secretPhrase = "secret-phrase";
 
         // check strategy while it's configured as ""
-        LdapBasicAuthenticator blankStrategyAuthenticator = new 
LdapBasicAuthenticator(new LdapSettings().setStrategy(""));
-        String strategyMaybeBlank = 
blankStrategyAuthenticator.getContextEnvironment(username, 
secret_phrase).get(Context.SECURITY_AUTHENTICATION);
+        LdapBasicAuthenticator blankStrategyAuthenticator = new 
LdapBasicAuthenticator(getNonSSLPreSettings().setStrategy(""));
+        String strategyMaybeBlank = 
blankStrategyAuthenticator.getContextEnvironment(username, 
secretPhrase).get(Context.SECURITY_AUTHENTICATION);
         Assert.assertNull("unexpected strategy", strategyMaybeBlank);
     }
+
+    @Test
+    public void testGetContextEnvironmentNormalWithSSL() {
+        String username = "username";
+        String secretPhrase = "secret-phrase";
+        Hashtable<String, String> env = 
AUTHENTICATOR_FOR_UTIL_METHODS_WITH_SSL.getContextEnvironment(username, 
secretPhrase);
+
+        Assert.assertEquals("unexpected ldap context factory name", 
LDAP_CTX_FACTORY_NAME, env.get(Context.INITIAL_CONTEXT_FACTORY));
+        Assert.assertEquals("unexpected ldap service provider url", 
LDAP_SERVICE_PROVIDER_SSL_URL, env.get(Context.PROVIDER_URL));
+        Assert.assertEquals("unexpected connecting timeout value", 
String.valueOf(Duration.parse(CONNECTING_TIMEOUT_VALUE).toMilliseconds()), 
env.get(LDAP_CONNECT_TIMEOUT_KEY));
+        Assert.assertEquals("unexpected reading timeout value", 
String.valueOf(Duration.parse(READING_TIMEOUT_VALUE).toMilliseconds()), 
env.get(LDAP_READ_TIMEOUT_KEY));
+        Assert.assertEquals("unexpected username", username+USERNAME_SUFFIX, 
env.get(Context.SECURITY_PRINCIPAL));
+        Assert.assertEquals("unexpected secret credentials", secretPhrase, 
env.get(Context.SECURITY_CREDENTIALS));
+        Assert.assertEquals("unexpected strategy", STRATEGY_SIMPLE, 
env.get(Context.SECURITY_AUTHENTICATION));
+        Assert.assertEquals("unexpected key-store path", 
EXISTING_MOCK_FILE_PATH, System.getProperty(SYS_PROP_SSL_KEY_STORE));
+        Assert.assertEquals("unexpected trust-store path", 
EXISTING_MOCK_FILE_PATH, System.getProperty(SYS_PROP_SSL_TRUST_STORE));
+    }
+
+    @Test
+    public void testGetContextEnvironmentMeaninglessCAPathSSL() {
+        String username = "username";
+        String secretPhrase = "secret-phrase";
+
+        // check null certificateAbsolutePath
+        try {
+            LdapBasicAuthenticator blankStrategyAuthenticator = new 
LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath(null));
+            blankStrategyAuthenticator.getContextEnvironment(username, 
secretPhrase).get(Context.SECURITY_AUTHENTICATION);
+        }
+        catch (Exception e) {
+            Assert.assertEquals("unexpected exception thrown", 
RuntimeException.class, e.getClass());
+            Assert.assertEquals("unexpected exception message", "The attribute 
'certificateAbsolutePath' must be set when using ldap over ssl to 
authenticate.", e.getMessage());
+        }
+
+        // check "" certificateAbsolutePath
+        try {
+            LdapBasicAuthenticator blankStrategyAuthenticator = new 
LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath(""));
+            blankStrategyAuthenticator.getContextEnvironment(username, 
secretPhrase).get(Context.SECURITY_AUTHENTICATION);
+        }
+        catch (Exception e) {
+            Assert.assertEquals("unexpected exception thrown", 
RuntimeException.class, e.getClass());
+            Assert.assertEquals("unexpected exception message", "The attribute 
'certificateAbsolutePath' must be set when using ldap over ssl to 
authenticate.", e.getMessage());
+        }
+    }
+
+    @Test
+    public void testGetContextEnvironmentUnexistingCA_SSL() {
+        String username = "username";
+        String secretPhrase = "secret-phrase";
+        String wrongCAPath = String.format("%s/this/cannot/be/existing", 
System.getProperty("user.dir"));
+        try {
+            // check with not existing path indicated by 
certificateAbsolutePath
+            LdapBasicAuthenticator blankStrategyAuthenticator = new 
LdapBasicAuthenticator(getSSLPreSettings().setCertificateAbsolutePath(wrongCAPath));
+            blankStrategyAuthenticator.getContextEnvironment(username, 
secretPhrase).get(Context.SECURITY_AUTHENTICATION);
+        }
+        catch (Exception e) {
+            Assert.assertEquals("unexpected exception thrown", 
RuntimeException.class, e.getClass());
+            Assert.assertEquals("unexpected exception message", 
String.format("The file specified not existing: %s", wrongCAPath), 
e.getMessage());
+        }
+    }
+
+    private static LdapSettings getNonSSLPreSettings() {
+        return new LdapSettings().setProviderUrl(LDAP_SERVICE_PROVIDER_URL);
+    }
+
+    private static LdapSettings getSSLPreSettings() {
+        return new 
LdapSettings().setProviderUrl(LDAP_SERVICE_PROVIDER_SSL_URL);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/ce8e1c50/eagle-server/src/test/resources/configuration.yml
----------------------------------------------------------------------
diff --git a/eagle-server/src/test/resources/configuration.yml 
b/eagle-server/src/test/resources/configuration.yml
index 8d388b0..eabb8a1 100644
--- a/eagle-server/src/test/resources/configuration.yml
+++ b/eagle-server/src/test/resources/configuration.yml
@@ -66,6 +66,10 @@ auth:
     # default or leave it a blank string.
     strategy: simple
 
+    # the absolute path of ssl certificate file. This attribute is required 
conditional only when the auth -> mode is set
+    # as "ldap" and providerUrl starting with "ldaps://".
+    certificateAbsolutePath: /certificate/absolute/path
+
     # timeout expression for connecting to ldap service endpoint
     connectingTimeout: 500ms
 

Reply via email to