Commonize & simplify LDAP and LDAPS auth
This makes it easy for further instances and projects
outside the IDS to use Kustvakt-full with their own LDAP.
Change-Id: I710f50079348d6cff9fd33376aebda33bc9f408e
diff --git a/full/pom.xml b/full/pom.xml
index 4b982be..5b2ecfb 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -86,6 +86,9 @@
<include>**/*.info</include>
<include>**/*.properties</include>
<include>**/*.json</include>
+ <include>**/*.p12</include>
+ <include>**/*.jks</include>
+ <include>**/*.ldif</include>
</includes>
</testResource>
</testResources>
@@ -134,11 +137,16 @@
<argLine>-Xmx512m -XX:MaxPermSize=256m
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager</argLine>
<excludes>
- <exclude>de/ids_mannheim/korap/authentication/*.java</exclude>
+ <exclude>de/ids_mannheim/korap/authentication/APIAuthenticationTest.java</exclude>
+ <exclude>de/ids_mannheim/korap/authentication/AuthenticationFilterTest.java</exclude>
+ <exclude>de/ids_mannheim/korap/authentication/Kustvakt*.java</exclude>
+ <exclude>de/ids_mannheim/korap/authentication/LdapTest.java</exclude>
+ <exclude>de/ids_mannheim/korap/authentication/Random*.java</exclude>
<exclude>de/ids_mannheim/korap/web/controller/TokenExpiryTest.java</exclude>
</excludes>
<includes>
<include>de/ids_mannheim/korap/**/*.java</include>
+ <include>de/ids_mannheim/korap/authentication/LdapAuth3Test.java</include>
</includes>
</configuration>
</plugin>
@@ -284,5 +292,13 @@
<version>5.13.2</version>
<scope>test</scope>
</dependency>
+
+ <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-text</artifactId>
+ <version>1.9</version>
+ </dependency>
+
</dependencies>
</project>
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
index 810d22b..53c8a1b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
@@ -1,511 +1,221 @@
-/* - Klasse zum Implementieren einer Benutzer-Authentifikation mittels LDAP
- * in der IDM-Datenbank (Identit�tsmanagement) von Eric Seubert, IDS.
- * - externe Bibliothek ist Novel JLDAP.
- * 27.01.17/FB
- *
- * Sourcen:
- * - https://www.novell.com/documentation/developer/samplecode/jldap_sample/VerifyPassword.java.html
- * - https://www.novell.com/documentation/developer/samplecode/jldap_sample/LDAPOIDs.java.html
- * - https://www.novell.com/documentation/developer/jldap/jldapenu/data/a90352e.html
- * WICHTIG:
- * - Novell-Bibliothek liefert 0 Treffer, wenn man nacheinander sucht!
- * Grund daf�r nicht gefunden.
- *
- * Version von unboundID - 19.04.17/FB
- *
- * UnboundID LDAP SDK For Java � 3.2.1
- * The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for
- * communicating with LDAP directory servers and performing related tasks like reading and writing LDIF,
- * encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package
- * contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for
- * communicating with LDAPv3 directory servers.
- * TODO:
- * - gesichertes Login mit gesch�tztem Passwort.
- * - Passwort des Admin verschl�sseln.
+/*
+ * user authentication via LDAP
*/
-
+
package de.ids_mannheim.korap.authentication;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.Properties;
-
import com.nimbusds.jose.JOSEException;
-import com.unboundid.ldap.sdk.Attribute;
-import com.unboundid.ldap.sdk.LDAPConnection;
-import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.LDAPSearchException;
-import com.unboundid.ldap.sdk.SearchResult;
-import com.unboundid.ldap.sdk.SearchResultEntry;
-import com.unboundid.ldap.sdk.SearchScope;
-
+import com.unboundid.ldap.sdk.*;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+import com.unboundid.util.ssl.TrustStoreTrustManager;
import de.ids_mannheim.korap.config.FullConfiguration;
import de.ids_mannheim.korap.constant.TokenType;
+import org.apache.commons.text.StringSubstitutor;
+
+import javax.net.ssl.SSLSocketFactory;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
/**
- * LDAP Login Tests
- *
- * @author bodmer, margaretha
+ * LDAP Login
+ *
+ * @author bodmer, margaretha, kupietz
* @see APIAuthentication
*/
public class LdapAuth3 extends APIAuthentication {
- /* For SSL Connection to LDAP, see: https://www.novell.com/documentation/developer/jldap/jldapenu/data/cchcbejj.html.
- * and use DEFAULT_SSL_PORT.
- * For now, plain text connection is used.
- * FB
- */
- final static Boolean DEBUGLOG = false; // log debug output.
- final static String attC2 = "idsC2"; // if value == TRUE: registered for COSMAS II (KorAP) Service.
- final static String attStatus = "idsStatus"; // value must be 0..2, 3 = locked account.
- final static String attEmail = "mail"; // attribute whose value is the requested email.
- final static int ldapPort = 389; //LDAPConnection.DEFAULT_PORT;
- // final static int ldapVersion = LDAPConnection.LDAP_V3;
- final static String ldapHost = "ldap.ids-mannheim.de";
- final static String ldapBase = "dc=ids-mannheim,dc=de";
- final static String sLoginDN = "cn=casaling,dc=ids-mannheim,dc=de";
- static String sPwd = null;
+ public static final int LDAP_AUTH_ROK = 0;
+ public static final int LDAP_AUTH_RCONNECT = 1; // cannot connect to LDAP Server
+ public static final int LDAP_AUTH_RINTERR = 2; // internal error: cannot verify User+Pwd.
+ /* cannot be distinguished, currently
+ public static final int LDAP_AUTH_RUNKNOWN = 3; // User Account or Pwd unknown;
+ public static final int LDAP_AUTH_RLOCKED = 4; // User Account locked;
+ public static final int LDAP_AUTH_RNOTREG = 5; // User known, but has not registered to KorAP/C2 Service yet;
+ */
+ public static final int LDAP_AUTH_RNOEMAIL = 6; // cannot obtain email for sUserDN
+ public static final int LDAP_AUTH_RNAUTH = 7; // User Account or Pwd unknown, or not authorized
+ final static Boolean DEBUGLOG = false; // log debug output.
- /**
- * return codes for functions of this class:
- */
-
- public static final int LDAP_AUTH_ROK = 0;
- public static final int LDAP_AUTH_RCONNECT = 1; // cannot connect to LDAP Server
- public static final int LDAP_AUTH_RINTERR = 2; // internal error: cannot verify User+Pwd.
- public static final int LDAP_AUTH_RUNKNOWN = 3; // User Account or Pwd unknown;
- public static final int LDAP_AUTH_RLOCKED = 4; // User Account locked;
- public static final int LDAP_AUTH_RNOTREG = 5; // User known, but has not registered to KorAP/C2 Service yet;
- public static final int LDAP_AUTH_RNOEMAIL = 6; // cannot obtain email for sUserDN.
-
- public LdapAuth3 (FullConfiguration config) throws JOSEException {
+ public LdapAuth3(FullConfiguration config) throws JOSEException {
super(config);
- }
-
-
- @Override
- public TokenType getTokenType () {
- return TokenType.API;
- }
-
- /**
- * getErrMessage:
- * returns String Message for LDAP_AUTH_Rxxx code.
- * @date 20.04.17/FB
- * @param code
- * @return Message in string form.
- */
- public static String getErrMessage(int code)
-
- {
- switch(code)
- {
- case LDAP_AUTH_ROK:
- return "LDAP Authentication successfull.";
- case LDAP_AUTH_RCONNECT:
- return "LDAP Authentication: connecting to LDAP Server failed!";
- case LDAP_AUTH_RINTERR:
- return "LDAP Authentication failed due to an internal error!";
- case LDAP_AUTH_RUNKNOWN:
- return "LDAP Authentication failed due to unknown user or password!";
- case LDAP_AUTH_RLOCKED:
- return "LDAP Authentication: known user is locked!";
- case LDAP_AUTH_RNOTREG:
- return "LDAP Authentication: known user has not registered yet for COSMAS II/KorAP!";
- case LDAP_AUTH_RNOEMAIL:
- return "LDAP Authentication: known user, but cannot obtain email!";
- default:
- return "LDAP Authentication failed with unknown error code!";
- }
- } // getErrMessage
-
- /**
- * ldapCode2StatusCode:
- * - converts a LDAP_AUTH_xxx Error Code to an Error Code of StatusCode.java.
- * @param base : Base value inside of StatusCode.java reserved for LDAP_AUTH Error Codes.
- * @param ldapErrCode : the LDAP_AUTH Error code
- * @return the StatusCode in the range reserved for LDAP_AUTH Errors.
- * @date 21.04.17/FB
- */
- public int ldapCode2StatusCode(int base, int ldapErrCode)
-
- {
- return base + ldapErrCode;
- } // ldapCode2StatusCode
-
- /*
- * load properties for LDAP Handling.
- * 17.02.17/FB
- */
-
- static String loadProp(String sConfFile) throws IOException
-
- {
- String sPwd = null;
- FileInputStream in;
- Properties prop;
-
+ }
+
+ public static String getErrMessage(int code) {
+ switch (code) {
+ case LDAP_AUTH_ROK:
+ return "LDAP Authentication successful.";
+ case LDAP_AUTH_RCONNECT:
+ return "LDAP Authentication: connecting to LDAP Server failed!";
+ case LDAP_AUTH_RINTERR:
+ return "LDAP Authentication failed due to an internal error!";
+/* cannot be distinguished, currently
+ case LDAP_AUTH_RUNKNOWN:
+ return "LDAP Authentication failed due to unknown user or password!";
+ case LDAP_AUTH_RLOCKED:
+ return "LDAP Authentication: known user is locked!";
+ case LDAP_AUTH_RNOTREG:
+ return "LDAP Authentication: known user has not registered yet!";
+*/
+ case LDAP_AUTH_RNOEMAIL:
+ return "LDAP Authentication: known user, but cannot obtain email!";
+ case LDAP_AUTH_RNAUTH:
+ return "LDAP Authentication: unknown user or password, or user is locked or not authorized!";
+ default:
+ return "LDAP Authentication failed with unknown error code!";
+ }
+ }
+
+ static HashMap<String, String> typeCastConvert(Properties prop) {
+ Map<String, String> step2 = (Map<String, String>) (Map) prop;
+ return new HashMap<>(step2);
+ }
+
+ static HashMap<String, String> loadProp(String sConfFile) throws IOException {
+ FileInputStream in;
+ Properties prop;
+
try {
in = new FileInputStream(sConfFile);
- }
- catch( IOException ex )
- {
- System.err.printf("Error: LDAP.loadProp: cannot load Property file '%s'!\n", sConfFile);
+ } catch (IOException ex) {
+ System.err.printf("Error: LDAP.loadProp: cannot load Property file '%s'!\n", sConfFile);
ex.printStackTrace();
return null;
- }
+ }
- if( DEBUGLOG ) System.out.println("Debug: loaded: " + sConfFile);
-
+ if (DEBUGLOG) System.out.println("Debug: loaded: " + sConfFile);
+
prop = new Properties();
- Enumeration<?> e;
-
+
try {
prop.load(in);
- e = prop.propertyNames();
+ return typeCastConvert(prop);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
- while( e.hasMoreElements() )
- {
- String key = (String)e.nextElement();
- String val = prop.getProperty(key);
- if( key.compareTo("pwd") == 0 )
- return val;
-
- //System.out.println("Property '" + key + "' = '" + val + "'.");
+ return new HashMap<>();
+ }
+
+ public static int login(String sUserDN, String sUserPwd, String ldapConfigFilename) throws LDAPException {
+
+ sUserDN = Filter.encodeValue(sUserDN);
+ sUserPwd = Filter.encodeValue(sUserPwd);
+
+ Map<String, String> ldapConfig;
+ try {
+ ldapConfig = loadProp(ldapConfigFilename);
+ } catch (IOException e) {
+ System.out.println("Error: LDAPAuth.login: cannot load Property file!");
+ return LDAP_AUTH_RINTERR;
+ }
+
+ final Boolean ldapS = Boolean.parseBoolean(ldapConfig.getOrDefault("ldapS", "false"));
+ final String ldapHost = ldapConfig.getOrDefault("ldapHost", "localhost");
+ final int ldapPort = Integer.parseInt(ldapConfig.getOrDefault("ldapPort", (ldapS ? "636" : "389")));
+ final String ldapBase = ldapConfig.getOrDefault("ldapBase", "dc=example,dc=com");
+ final String sLoginDN = ldapConfig.getOrDefault("sLoginDN", "cn=admin,dc=example,dc=com");
+ final String ldapFilter = ldapConfig.getOrDefault("ldapFilter", "(&(|(&(mail=${username})(idsC2Password=${password}))(&(idsC2Profile=${username})(idsC2Password=${password})))(&(idsC2=TRUE)(|(idsStatus=1)(|(idsStatus=0)(xidsStatus=\00)))))");
+ final String sPwd = ldapConfig.getOrDefault("pwd", "");
+ final String trustStorePath = ldapConfig.getOrDefault("trustStore", null);
+
+ Map<String, String> valuesMap = new HashMap<>();
+ valuesMap.put("username", sUserDN);
+ valuesMap.put("password", sUserPwd);
+ StringSubstitutor sub = new StringSubstitutor(valuesMap);
+ String ldapFilterInstance = sub.replace(ldapFilter);
+
+ if (DEBUGLOG) {
+ //System.out.printf("LDAP Version = %d.\n", LDAPConnection.LDAP_V3);
+ System.out.printf("LDAP Host & Port = '%s':%d.\n", ldapHost, ldapPort);
+ System.out.printf("Login User = '%s'\n", sUserDN);
+ }
+
+ // LDAP Connection:
+ if (DEBUGLOG) System.out.println("LDAPS " + ldapS);
+
+ LDAPConnection lc = null;
+
+ if (ldapS) {
+ try {
+ SSLUtil sslUtil;
+ if (trustStorePath != null && !trustStorePath.isEmpty()) {
+ sslUtil = new SSLUtil(new TrustStoreTrustManager(trustStorePath));
+ } else {
+ sslUtil = new SSLUtil(new TrustAllTrustManager());
}
- }
- catch( IOException ex )
- {
- ex.printStackTrace();
- }
+ SSLSocketFactory socketFactory = sslUtil.createSSLSocketFactory();
+ lc = new LDAPConnection(socketFactory, ldapHost, ldapPort);
+ } catch (GeneralSecurityException e) {
+ System.err.printf("Error: login: Connecting to LDAPS Server: failed: '%s'!\n", e);
+ return ldapTerminate(lc, LDAP_AUTH_RCONNECT);
+ }
+ } else {
+ lc = new LDAPConnection();
+ try {
+ lc.connect(ldapHost, ldapPort);
+ if (DEBUGLOG && ldapS) System.out.println("LDAPS Connection = OK\n");
+ if (DEBUGLOG && !ldapS) System.out.println("LDAP Connection = OK\n");
+ } catch (LDAPException e) {
+ System.err.printf("Error: login: Connecting to LDAP Server: failed: '%s'!\n", e);
+ return ldapTerminate(lc, LDAP_AUTH_RCONNECT);
+ }
+ }
- return sPwd;
- } // loadProp
+ if (DEBUGLOG) System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
- /**
- * ldapLogin
- * Arguments:
- * sUserDN : either COSMAS II specific Account Name or IDS wide (IDM) account name;
- * sUserPwd : either COSMAS II specific Password or IDS wide (IDM) password;
- * return : 0 = OK, User Account + Pwd are OK, no restrictions;
- * 1 = internal error: cannot verify User+Pwd;
- * 2 = User Account or Pwd unknown;
- * 3 = User Account locked;
- * 4 = User known, but has not registered to KorAP/C2 Service yet;
- * LDAP Attributes that are checked (definition by Eric Seubert, 02.02.17):
- * idsC2 = TRUE -> Zugang zu C2 (registriert und zugelassen)
- * idsC2 = FALSE (bzw Attribut nicht vorhanden)
- * -> kein Zugang zu C2 (nicht zugelassen, egal ob registriert oder nicht)
- *
- * idsStatus = 0 -> Nutzerkennung OK;
- * idsStatus = 1 -> Nutzer ist kein aktiver IDS-Mitarbeiter
- * idsStatus = 3 -> Nutzer ist LDAP-weit gesperrt
- */
+ try {
+ // bind to server:
+ if (DEBUGLOG) System.out.printf("Binding with '%s' ...\n", sLoginDN);
+ lc.bind(sLoginDN, sPwd);
+ if (DEBUGLOG) System.out.printf("Binding: OK.\n");
+ } catch (LDAPException e) {
+ System.err.printf("Error: login: Binding failed: '%s'!\n", e);
+ return ldapTerminate(lc, LDAP_AUTH_RINTERR);
+ }
- public static int login(String sUserDN, String sUserPwd, String ldapConfig) throws LDAPException
+ if (DEBUGLOG) System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
- {
- String sUserC2DN = sUserDN;
- String sUserC2Pwd = sUserPwd;
+ if (DEBUGLOG) System.out.printf("Finding user '%s'...\n", sUserDN);
- /* login with e-mail - 15.09.21/FB:
- */
- String ldapFilter = String.format("(|(&(mail=%s)(userPassword=%s))(&(uid=%s)(userPassword=%s))(&(idsC2Profile=%s)(idsC2Password=%s)))",
- sUserDN, sUserPwd, sUserDN, sUserPwd, sUserC2DN, sUserC2Pwd);
- /* without e-mail login:
- * String ldapFilter = String.format("(|(&(uid=%s)(userPassword=%s))(&(idsC2Profile=%s)(idsC2Password=%s)))",
- sUserDN, sUserPwd, sUserC2DN, sUserC2Pwd);
- */
- SearchResult srchRes = null;
+ SearchResult srchRes;
+ try {
+ // SCOPE_SUB = Scope Subtree.
+ if (DEBUGLOG) System.out.printf("Finding Filter: '%s'.\n", ldapFilterInstance);
- try{
- sPwd = loadProp(ldapConfig);
- }
- catch( IOException e )
- {
- System.out.println("Error: LDAPAuth.login: cannot load Property file!");
- return LDAP_AUTH_RINTERR;
- }
-
- if( DEBUGLOG )
- {
- //System.out.printf("LDAP Version = %d.\n", LDAPConnection.LDAP_V3);
- System.out.printf("LDAP Host & Port = '%s':%d.\n", ldapHost, ldapPort);
- System.out.printf("Login User & Pwd = '%s' + '%s'\n", sUserDN, sUserPwd);
- }
+ srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilterInstance);
- // LDAP Connection:
- if( DEBUGLOG ) System.out.println("");
+ if (DEBUGLOG) System.out.printf("Finding '%s': %d entries.\n", sUserDN, srchRes.getEntryCount());
+ } catch (LDAPSearchException e) {
+ System.err.printf("Error: login: Search for User failed: '%s'!\n", e);
+ return ldapTerminate(lc, LDAP_AUTH_RNAUTH);
+ }
- LDAPConnection lc = new LDAPConnection();
- try {
- // connect to LDAP Server:
- lc.connect(ldapHost, ldapPort);
- if( DEBUGLOG ) System.out.println("LDAP Connection = OK\n");
- }
- catch( LDAPException e)
- {
- System.err.printf("Error: login: Connecting to LDAP Server: failed: '%s'!\n", e.toString());
- return ldapTerminate(lc, LDAP_AUTH_RCONNECT);
- }
+ if (srchRes.getEntryCount() == 0) {
+ if (DEBUGLOG) System.out.printf("Finding '%s': no entry found!\n", sUserDN);
+ return ldapTerminate(lc, LDAP_AUTH_RNAUTH);
+ }
- if( DEBUGLOG )
- System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
-
- try {
- // bind to server:
- if( DEBUGLOG ) System.out.printf("Binding with '%s' + '%s'...\n", sLoginDN, sPwd);
- lc.bind(sLoginDN, sPwd);
- if( DEBUGLOG ) System.out.printf("Binding: OK.\n");
- }
- catch( LDAPException e )
- {
- System.err.printf("Error: login: Binding failed: '%s'!\n", e.toString());
- return ldapTerminate(lc, LDAP_AUTH_RINTERR);
- }
+ return ldapTerminate(lc, LDAP_AUTH_ROK); // OK.
+ }
- if( DEBUGLOG )
- System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
-
- if( DEBUGLOG ) System.out.printf("Finding user '%s'...\n", sUserDN);
- try{
- // SCOPE_SUB = Scope Subtree.
- if( DEBUGLOG ) System.out.printf("Finding Filter: '%s'.\n", ldapFilter);
+ public static int ldapTerminate(LDAPConnection lc, int ret) {
+ if (DEBUGLOG) System.out.println("Terminating...");
- // hier werden alle Attribute abgefragt:
- //srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilter, null);
- // wir fragen nur diese Attribute ab:
- srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilter, attStatus, attC2);
+ lc.close(null);
+ if (DEBUGLOG) System.out.println("closing connection: done.\n");
+ return ret;
+ }
- if( DEBUGLOG ) System.out.printf("Finding '%s': %d entries.\n", sUserDN, srchRes.getEntryCount());
- }
- catch( LDAPSearchException e )
- {
- System.err.printf("Error: login: Search for User failed: '%s'!\n", e.toString());
- return ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
- }
-
- if( srchRes.getEntryCount() == 0 )
- {
- if( DEBUGLOG ) System.out.printf("Finding '%s': no entry found!\n", sUserDN);
- return ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
- }
-
- if( DEBUGLOG ) System.out.println("Display results:");
-
- Boolean
- bStatus = false,
- bC2 = false;
-
- // Attribute pr�fen:
- for (SearchResultEntry e : srchRes.getSearchEntries())
- {
- for( Attribute attr : e.getAttributes() )
- {
- Integer val;
-
- if( DEBUGLOG )
- System.out.printf(" att: '%s'='%s'.\n", attr.getName(), attr.getValue());
-
- // checking pertinent attribut/value pairs:
- // "idsStatus": values 0=OK, 1=inaktiv=OK, 2-3 = locked account.
- if( attr.getName().equals(attStatus) )
- {
- if( (val = attr.getValueAsInteger()) == null || (val != 0 && val != 1) )
- {
- if( DEBUGLOG ) System.out.printf("idsStatus = '%s' -> User locked!\n", attr.getValue());
- return ldapTerminate(lc, LDAP_AUTH_RLOCKED);
- }
- if( DEBUGLOG ) System.out.printf(" att: '%s'='%s': OK.\n", attr.getName(), attr.getValue());
- bStatus = true;
- }
-
- // "c2IDS" must be set to "TRUE" = User known, but has not yet registered to C2 Service -> KorAP Service.
- if( attr.getName().equals(attC2) )
- {
- if( attr.getValue().equals("FALSE") )
- {
- if( DEBUGLOG )
- System.out.printf("idsC2 = '%s'-> User known, but has not registered C2/KorAP Service yet!\n",
- attr.getValue());
- return ldapTerminate(lc, LDAP_AUTH_RNOTREG);
- }
- if( DEBUGLOG )
- System.out.printf(" att: idsC2 = '%s'-> registered User: OK.\n", attr.getValue());
- bC2 = true;
- }
- }
-
- if( DEBUGLOG ) System.out.println();
- }
-
- if( bStatus == true && bC2 == true )
- {
- return ldapTerminate(lc, LDAP_AUTH_ROK); // OK.
- }
- else
- return ldapTerminate(lc, LDAP_AUTH_RNOTREG); // Attribute konnten nicht gepr�ft werden.
-
- } // ldapLogin
-
- /**
- * getEmail():
- *
- * Arguments:
- * sUserDN : either COSMAS II specific Account Name or IDS wide (IDM) account name;
- * ldapConfig : path+file name of LDAP configuration file.
- *
- * Returns : the requested Email of sUserDN.
- * Notices:
- * - no password needed. Assuming that sUserDN is already authorized and active.
- *
- *
- * 16.09.21/FB
- */
-
- public static String getEmail(String sUserDN, String ldapConfig) throws LDAPException
-
- {
- final String func = "LdapAuth3.getEmail";
-
- // sUSerDN is either C2/KorAP specific account name or the IDS wide account name:
- String ldapFilter = String.format("(|(uid=%s)(idsC2Profile=%s))", sUserDN, sUserDN);
-
- SearchResult srchRes = null;
-
- try{
- sPwd = loadProp(ldapConfig);
- }
- catch( IOException e )
- {
- System.err.printf("Error: %s: cannot load Property file '%s'!", func, ldapConfig);
- return null;
- }
-
- if( DEBUGLOG )
- {
- //System.out.printf("LDAP Version = %d.\n", LDAPConnection.LDAP_V3);
- System.out.printf("%s: LDAP Host & Port = '%s':%d.\n", func, ldapHost, ldapPort);
- System.out.printf("%s: User Account = '%s'\n", func, sUserDN);
- }
-
- // LDAP Connection:
- if( DEBUGLOG ) System.out.println("");
-
- LDAPConnection
- lc = new LDAPConnection();
-
- try {
- // connect to LDAP Server:
- lc.connect(ldapHost, ldapPort);
- if( DEBUGLOG ) System.out.println("LDAP Connection = OK\n");
- }
- catch( LDAPException e)
- {
- System.err.printf("Error: %s: Connecting to LDAP Server: failed: '%s'!\n", func, e.toString());
- ldapTerminate(lc, LDAP_AUTH_RCONNECT);
- return null;
- }
-
- if( DEBUGLOG )
- System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
-
- try {
- // bind to server:
- if( DEBUGLOG ) System.out.printf("Binding with '%s' + '%s'...\n", sLoginDN, sPwd);
- lc.bind(sLoginDN, sPwd);
- if( DEBUGLOG ) System.out.printf("Binding: OK.\n");
- }
- catch( LDAPException e )
- {
- System.err.printf("Error: %s: Binding failed: '%s'!\n", func, e.toString());
- ldapTerminate(lc, LDAP_AUTH_RINTERR);
- return null;
- }
-
- if( DEBUGLOG )
- System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
-
- if( DEBUGLOG ) System.out.printf("Finding user '%s'...\n", sUserDN);
- try{
- // SCOPE_SUB = Scope Subtree.
- if( DEBUGLOG ) System.out.printf("Finding Filter: '%s'.\n", ldapFilter);
-
- // requested attribute is attEmail:
- srchRes = lc.search(ldapBase, SearchScope.SUB, ldapFilter, attEmail);
-
- if( DEBUGLOG ) System.out.printf("Finding '%s': %d entries.\n", sUserDN, srchRes.getEntryCount());
- }
- catch( LDAPSearchException e )
- {
- System.err.printf("Error: %s: Search for User '%s' failed: '%s'!\n", func, sUserDN, e.toString());
- ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
- return null;
- }
-
- if( srchRes.getEntryCount() == 0 )
- {
- if( DEBUGLOG ) System.out.printf("Error: %s: account '%s': 0 entries found!\n", func, sUserDN);
- ldapTerminate(lc, LDAP_AUTH_RUNKNOWN);
- return null;
- }
-
- if( DEBUGLOG ) System.out.printf("Debug: %s: Extract email from results.\n", func);
-
- // Now get email from result.
- // expected: 1 result with 1 attribute value:
-
- SearchResultEntry
- e;
- Attribute
- attr;
- String
- email;
-
- if( (e = srchRes.getSearchEntries().get(0)) != null &&
- (attr = e.getAttribute(attEmail)) != null &&
- (email = attr.getValue()) != null )
- {
- // return email:
- if( DEBUGLOG )
- System.out.printf("Debug: %s: account '%s' has email = '%s'.\n", func, sUserDN, email);
- ldapTerminate(lc, LDAP_AUTH_ROK); // OK.
- return email;
- }
-
- // cannot obtain email from result:
- System.err.printf("Error: %s: account '%s': no attribute '%s' for email found!\n", func, sUserDN, attEmail);
-
- ldapTerminate(lc, LDAP_AUTH_RNOEMAIL); // no email found.
- return null;
- } // getEmail
-
-/**
- * ldapTerminate
- */
-
-public static int ldapTerminate(LDAPConnection lc, int ret)
-
- {
- if( DEBUGLOG ) System.out.println("Terminating...");
- /*
- try{
- lc.finalize();
- if( DEBUGLOG ) System.out.println("Debug: finalize: OK.");
- }
- catch( LDAPException e )
- {
- System.out.printf("finalize failed: '%s'!\n", e.toString());
- }
- */
-
- lc.close(null);
- if( DEBUGLOG ) System.out.println("closing connection: done.\n");
- return ret;
- } // ldapTerminate
+ @Override
+ public TokenType getTokenType() {
+ return TokenType.API;
+ }
}
-
diff --git a/full/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java b/full/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
new file mode 100644
index 0000000..dc9e3ae
--- /dev/null
+++ b/full/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
@@ -0,0 +1,135 @@
+package de.ids_mannheim.korap.authentication;
+
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.listener.InMemoryListenerConfig;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.util.Base64;
+import com.unboundid.util.StaticUtils;
+import com.unboundid.util.ssl.KeyStoreKeyManager;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+import com.unboundid.util.ssl.TrustStoreTrustManager;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.security.GeneralSecurityException;
+
+import static de.ids_mannheim.korap.authentication.LdapAuth3.LDAP_AUTH_ROK;
+import static de.ids_mannheim.korap.authentication.LdapAuth3.LDAP_AUTH_RNAUTH;
+import static org.junit.Assert.assertEquals;
+
+public class LdapAuth3Test {
+ public static final String TEST_LDAP_PROPERTIES = "src/test/resources/test-ldap.properties";
+ public static final String TEST_LDAPS_PROPERTIES = "src/test/resources/test-ldaps.properties";
+ public static final String TEST_LDAPS_TS_PROPERTIES = "src/test/resources/test-ldaps-with-truststore.properties";
+ public static final String TEST_LDAP_USERS_LDIF = "src/test/resources/test-ldap-users.ldif";
+ private static final String keyStorePath = "src/test/resources/keystore.p12";
+ static InMemoryDirectoryServer server;
+
+ @BeforeClass
+ public static void startDirectoryServer() throws LDAPException, GeneralSecurityException {
+ InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
+ config.addAdditionalBindCredentials("cn=admin,dc=example,dc=com", "adminpassword");
+ config.setSchema(null);
+
+ final SSLUtil serverSSLUtil = new SSLUtil(new KeyStoreKeyManager(keyStorePath, "password".toCharArray(), "PKCS12", "server-cert"), new TrustStoreTrustManager(keyStorePath));
+
+ final SSLUtil clientSslUtil = new SSLUtil(new TrustAllTrustManager());
+
+ config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", // Listener name
+ null, // Listen address. (null = listen on all interfaces)
+ 3268, // Listen port (0 = automatically choose an available port)
+ clientSslUtil.createSSLSocketFactory()), // StartTLS factory
+ InMemoryListenerConfig.createLDAPSConfig("LDAPS", // Listener name
+ null, // Listen address. (null = listen on all interfaces)
+ 3269, // Listen port (0 = automatically choose an available port)
+ serverSSLUtil.createSSLServerSocketFactory(), clientSslUtil.createSSLSocketFactory()));
+ server = new InMemoryDirectoryServer(config);
+
+ String configPath = TEST_LDAP_USERS_LDIF;
+ server.importFromLDIF(true, configPath);
+ server.startListening();
+ }
+
+ @AfterClass
+ public static void ShutDownDirectoryServer() {
+ server.shutDown(true);
+ }
+
+ @Test
+ public void testLoginWithUsername() throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("testuser", "topsecret", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testLoginWithUid() throws LDAPException {
+ final byte[] passwordBytes = StaticUtils.getBytes("password");
+ String pw = Base64.encode(passwordBytes);
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("testuser", pw, TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testLoginWithEmail() throws LDAPException {
+ final byte[] passwordBytes = StaticUtils.getBytes("password");
+ String pw = Base64.encode(passwordBytes);
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("testuser@example.com", pw, TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingLoginWithWrongEmail() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("notestuser@example.com", "topsecret", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingLoginWithEmailAndWrongPassword() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("testuser@example.com", "wrongpw", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingLoginWithUsernameAndWrongPassword() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("testuser", "wrongpw", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingLoginWithoutC2Attr() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("doe", "topsecret", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingLoginWithoutBadStatus() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("berserker", "topsecret", TEST_LDAP_PROPERTIES));
+ }
+
+ @Test
+ public void testSecureLoginWithUsername() throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("testuser", "topsecret", TEST_LDAPS_PROPERTIES));
+ }
+
+ @Test
+ public void testSecureLoginWithTrustStoreAndUsername() throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("testuser", "topsecret", TEST_LDAPS_TS_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingSecureLoginWithTrustStoreAndUsernameAndWrongPW() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("testuser", "topsecrets", TEST_LDAPS_TS_PROPERTIES));
+ }
+
+ @Test
+ public void testPasswordWithAsterisk() throws LDAPException {
+ assertEquals(LDAP_AUTH_ROK, LdapAuth3.login("test", "top*ecret", TEST_LDAPS_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingEscapedPW() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("testuser", "top*", TEST_LDAPS_TS_PROPERTIES));
+ }
+
+ @Test
+ public void testFailingIllegalPW() throws LDAPException {
+ assertEquals(LDAP_AUTH_RNAUTH, LdapAuth3.login("testuser", "*", TEST_LDAPS_TS_PROPERTIES));
+ }
+
+}
diff --git a/full/src/test/resources/keystore.p12 b/full/src/test/resources/keystore.p12
new file mode 100644
index 0000000..a1d7980
--- /dev/null
+++ b/full/src/test/resources/keystore.p12
Binary files differ
diff --git a/full/src/test/resources/test-ldap-users.ldif b/full/src/test/resources/test-ldap-users.ldif
new file mode 100644
index 0000000..a965181
--- /dev/null
+++ b/full/src/test/resources/test-ldap-users.ldif
@@ -0,0 +1,66 @@
+dn: dc=example,dc=com
+dc: example
+ou: people
+objectClass: dcObject
+objectClass: organizationalUnit
+
+dn: ou=people,dc=example,dc=com
+ou: people
+objectClass: organizationalUnit
+
+dn: uid=testuser,ou=people,dc=example,dc=com
+cn: Peter Testuser
+sn: Testuser
+givenName: Peter
+mail: testuser@example.com
+userPassword: cGFzc3dvcmQ=
+displayName: Dr. Peter Testuser
+idsC2: TRUE
+idsC2Profile: testuser
+idsC2Password: topsecret
+idsC2News: TRUE
+title: Herr
+uid: testuser
+
+dn: uid=test,ou=people,dc=example,dc=com
+cn: Peter Test
+sn: Test
+givenName: Peter
+mail: test@example.com
+userPassword: top*ecret
+displayName: Dr. Peter Test
+idsC2: TRUE
+idsStatus: 1
+idsC2Profile: test
+idsC2Password: top*ecret
+uid: test
+
+dn: uid=doe,ou=people,dc=example,dc=com
+cn: John Doe
+sn: doe
+givenName: John
+mail: doe@example.com
+userPassword: cGFzc3dvcmQ=
+displayName: Dr. John Doe
+idsStatus: 0
+idsC2: FALSE
+idsC2Profile: doe
+idsC2Password: topsecret
+idsC2News: TRUE
+title: Herr
+uid: doe
+
+dn: uid=berserker,ou=people,dc=example,dc=com
+cn: Bernd Berserker
+sn: berserker
+givenName: Joe
+mail: berserker@example.com
+userPassword: cGFzc3dvcmQ=
+displayName: berserk
+idsStatus: 2
+idsC2: TRUE
+idsC2Profile: doe
+idsC2Password: topsecret
+idsC2News: TRUE
+title: Herr
+uid: berserk
diff --git a/full/src/test/resources/test-ldap.properties b/full/src/test/resources/test-ldap.properties
new file mode 100644
index 0000000..aa27f14
--- /dev/null
+++ b/full/src/test/resources/test-ldap.properties
@@ -0,0 +1,6 @@
+ldapHost=localhost
+ldapPort=3268
+ldapBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+ldapFilter=(&(|(&(|(uid=${username})(mail=${username}))(userPassword=${password}))(&(idsC2Profile=${username})(idsC2Password=${password})))(&(idsC2=TRUE)(|(idsStatus=1)(|(idsStatus=0)(!(idsStatus=*))))))
diff --git a/full/src/test/resources/test-ldaps-with-truststore.properties b/full/src/test/resources/test-ldaps-with-truststore.properties
new file mode 100644
index 0000000..d785301
--- /dev/null
+++ b/full/src/test/resources/test-ldaps-with-truststore.properties
@@ -0,0 +1,8 @@
+ldapHost=localhost
+ldapPort=3269
+ldapS=true
+trustStore=src/test/resources/truststore.jks
+ldapBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+ldapFilter=(&(|(&(|(uid=${username})(mail=${username}))(userPassword=${password}))(&(idsC2Profile=${username})(idsC2Password=${password})))(&(idsC2=TRUE)(|(idsStatus=1)(|(idsStatus=0)(!(idsStatus=*))))))
diff --git a/full/src/test/resources/test-ldaps.properties b/full/src/test/resources/test-ldaps.properties
new file mode 100644
index 0000000..732076f
--- /dev/null
+++ b/full/src/test/resources/test-ldaps.properties
@@ -0,0 +1,8 @@
+ldapHost=localhost
+ldapPort=3269
+ldapS=true
+trustStore=
+ldapBase=dc=example,dc=com
+sLoginDN=cn=admin,dc=example,dc=com
+pwd=adminpassword
+ldapFilter=(&(|(&(|(uid=${username})(mail=${username}))(userPassword=${password}))(&(idsC2Profile=${username})(idsC2Password=${password})))(&(idsC2=TRUE)(|(idsStatus=1)(|(idsStatus=0)(!(idsStatus=*))))))
diff --git a/full/src/test/resources/truststore.jks b/full/src/test/resources/truststore.jks
new file mode 100644
index 0000000..50804be
--- /dev/null
+++ b/full/src/test/resources/truststore.jks
Binary files differ