blob: 8d3633f67072de3a1a353bafd21e600df21e16ed [file] [log] [blame]
Marc Kupietz0a378672022-04-30 09:35:27 +02001/*
2 * user authentication via LDAP
Bodmo3d6bd352017-04-25 11:31:39 +02003 */
Marc Kupietz0a378672022-04-30 09:35:27 +02004
margaretha139d0f72017-11-14 18:56:22 +01005package de.ids_mannheim.korap.authentication;
Bodmo3d6bd352017-04-25 11:31:39 +02006
margaretha235a6802018-06-06 19:21:53 +02007import com.nimbusds.jose.JOSEException;
Marc Kupietz0a378672022-04-30 09:35:27 +02008import com.unboundid.ldap.sdk.*;
Marc Kupietz7cb32132022-05-09 06:25:47 +02009import com.unboundid.util.NotNull;
Marc Kupietz0a378672022-04-30 09:35:27 +020010import com.unboundid.util.ssl.SSLUtil;
11import com.unboundid.util.ssl.TrustAllTrustManager;
12import com.unboundid.util.ssl.TrustStoreTrustManager;
margaretha5225ed02018-06-25 18:38:40 +020013import de.ids_mannheim.korap.config.FullConfiguration;
margaretha0e8f4e72018-04-05 14:11:52 +020014import de.ids_mannheim.korap.constant.TokenType;
Marc Kupietz1e388b42022-04-30 18:37:03 +020015import de.ids_mannheim.korap.server.EmbeddedLdapServer;
Marc Kupietz0a378672022-04-30 09:35:27 +020016import org.apache.commons.text.StringSubstitutor;
17
18import javax.net.ssl.SSLSocketFactory;
Marc Kupietz1e388b42022-04-30 18:37:03 +020019import java.net.UnknownHostException;
Marc Kupietz0a378672022-04-30 09:35:27 +020020import java.security.GeneralSecurityException;
Marc Kupietz9d599592022-05-01 16:29:18 +020021import java.util.*;
Marc Kupietz1e388b42022-04-30 18:37:03 +020022
Bodmo3d6bd352017-04-25 11:31:39 +020023
24/**
Marc Kupietz0a378672022-04-30 09:35:27 +020025 * LDAP Login
26 *
27 * @author bodmer, margaretha, kupietz
margaretha4de41192017-11-15 11:47:11 +010028 * @see APIAuthentication
Bodmo3d6bd352017-04-25 11:31:39 +020029 */
margaretha4de41192017-11-15 11:47:11 +010030public class LdapAuth3 extends APIAuthentication {
Bodmo3d6bd352017-04-25 11:31:39 +020031
Marc Kupietz0a378672022-04-30 09:35:27 +020032 public static final int LDAP_AUTH_ROK = 0;
33 public static final int LDAP_AUTH_RCONNECT = 1; // cannot connect to LDAP Server
34 public static final int LDAP_AUTH_RINTERR = 2; // internal error: cannot verify User+Pwd.
Marc Kupietz0a378672022-04-30 09:35:27 +020035 public static final int LDAP_AUTH_RUNKNOWN = 3; // User Account or Pwd unknown;
36 public static final int LDAP_AUTH_RLOCKED = 4; // User Account locked;
37 public static final int LDAP_AUTH_RNOTREG = 5; // User known, but has not registered to KorAP/C2 Service yet;
Marc Kupietz0a378672022-04-30 09:35:27 +020038 public static final int LDAP_AUTH_RNOEMAIL = 6; // cannot obtain email for sUserDN
39 public static final int LDAP_AUTH_RNAUTH = 7; // User Account or Pwd unknown, or not authorized
40 final static Boolean DEBUGLOG = false; // log debug output.
Bodmo3d6bd352017-04-25 11:31:39 +020041
Marc Kupietz0a378672022-04-30 09:35:27 +020042 public LdapAuth3(FullConfiguration config) throws JOSEException {
margaretha4de41192017-11-15 11:47:11 +010043 super(config);
Marc Kupietz0a378672022-04-30 09:35:27 +020044 }
45
46 public static String getErrMessage(int code) {
47 switch (code) {
48 case LDAP_AUTH_ROK:
49 return "LDAP Authentication successful.";
50 case LDAP_AUTH_RCONNECT:
51 return "LDAP Authentication: connecting to LDAP Server failed!";
52 case LDAP_AUTH_RINTERR:
53 return "LDAP Authentication failed due to an internal error!";
Marc Kupietz0a378672022-04-30 09:35:27 +020054 case LDAP_AUTH_RUNKNOWN:
55 return "LDAP Authentication failed due to unknown user or password!";
56 case LDAP_AUTH_RLOCKED:
57 return "LDAP Authentication: known user is locked!";
58 case LDAP_AUTH_RNOTREG:
Marc Kupietz7cb32132022-05-09 06:25:47 +020059 return "LDAP Authentication: known user, but not registered for this service!";
Marc Kupietz0a378672022-04-30 09:35:27 +020060 case LDAP_AUTH_RNOEMAIL:
61 return "LDAP Authentication: known user, but cannot obtain email!";
62 case LDAP_AUTH_RNAUTH:
63 return "LDAP Authentication: unknown user or password, or user is locked or not authorized!";
64 default:
65 return "LDAP Authentication failed with unknown error code!";
66 }
67 }
68
Marc Kupietz9a1188e2022-05-05 23:26:14 +020069 public static int login(String login, String password, String ldapConfigFilename) throws LDAPException {
70 LDAPConfig ldapConfig = new LDAPConfig(ldapConfigFilename);
Marc Kupietz0a378672022-04-30 09:35:27 +020071
Marc Kupietz9a1188e2022-05-05 23:26:14 +020072 login = Filter.encodeValue(login);
73 password = Filter.encodeValue(password);
Marc Kupietz0a378672022-04-30 09:35:27 +020074
Marc Kupietz9a1188e2022-05-05 23:26:14 +020075 if (ldapConfig.useEmbeddedServer) {
76 try {
77 EmbeddedLdapServer.startIfNotRunning(ldapConfig);
78 } catch (GeneralSecurityException | UnknownHostException | LDAPException e) {
79 throw new RuntimeException(e);
80 }
81 }
82
Marc Kupietz7cb32132022-05-09 06:25:47 +020083 LdapAuth3Result ldapAuth3Result = search(login, password, ldapConfig, !ldapConfig.searchFilter.contains("${password}"), true);
84 SearchResult srchRes = ldapAuth3Result.getSearchResultValue();
Marc Kupietz75e78282022-05-02 20:39:20 +020085
Marc Kupietz7cb32132022-05-09 06:25:47 +020086 if (ldapAuth3Result.getErrorCode() != 0 || srchRes == null || srchRes.getEntryCount() == 0) {
Marc Kupietz9a1188e2022-05-05 23:26:14 +020087 if (DEBUGLOG) System.out.printf("Finding '%s': no entry found!\n", login);
Marc Kupietz7cb32132022-05-09 06:25:47 +020088 return ldapAuth3Result.getErrorCode();
Marc Kupietz75e78282022-05-02 20:39:20 +020089 }
Marc Kupietz30925d82022-05-06 15:33:52 +020090
Marc Kupietz75e78282022-05-02 20:39:20 +020091 return LDAP_AUTH_ROK;
92 }
Marc Kupietz7cb32132022-05-09 06:25:47 +020093
94 @NotNull
95 public static LdapAuth3Result search(String login, String password, LDAPConfig ldapConfig, boolean bindWithFoundDN, boolean applyExtraFilters) {
Marc Kupietz0a378672022-04-30 09:35:27 +020096 Map<String, String> valuesMap = new HashMap<>();
Marc Kupietz9a1188e2022-05-05 23:26:14 +020097 valuesMap.put("login", login);
98 valuesMap.put("password", password);
Marc Kupietz0a378672022-04-30 09:35:27 +020099 StringSubstitutor sub = new StringSubstitutor(valuesMap);
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200100 String searchFilterInstance = sub.replace(ldapConfig.searchFilter);
101
102 valuesMap.clear();
103 valuesMap.put("login", login);
104 sub = new StringSubstitutor(valuesMap);
105 String insensitiveSearchFilter = sub.replace(ldapConfig.searchFilter);
Marc Kupietz0a378672022-04-30 09:35:27 +0200106
107 if (DEBUGLOG) {
108 //System.out.printf("LDAP Version = %d.\n", LDAPConnection.LDAP_V3);
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200109 System.out.printf("LDAP Host & Port = '%s':%d.\n", ldapConfig.host, ldapConfig.port);
110 System.out.printf("Login User = '%s'\n", login);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200111 System.out.println("LDAPS " + ldapConfig.useSSL);
Marc Kupietz0a378672022-04-30 09:35:27 +0200112 }
113
Marc Kupietz9d599592022-05-01 16:29:18 +0200114 LDAPConnection lc;
Marc Kupietz0a378672022-04-30 09:35:27 +0200115
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200116 if (ldapConfig.useSSL) {
Marc Kupietz0a378672022-04-30 09:35:27 +0200117 try {
118 SSLUtil sslUtil;
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200119 if (ldapConfig.trustStorePath != null && !ldapConfig.trustStorePath.isEmpty()) {
120 sslUtil = new SSLUtil(new TrustStoreTrustManager(ldapConfig.trustStorePath));
Marc Kupietz0a378672022-04-30 09:35:27 +0200121 } else {
122 sslUtil = new SSLUtil(new TrustAllTrustManager());
Bodmo3d6bd352017-04-25 11:31:39 +0200123 }
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200124 if (ldapConfig.additionalCipherSuites != null && !ldapConfig.additionalCipherSuites.isEmpty()) {
125 addSSLCipherSuites(ldapConfig.additionalCipherSuites);
Marc Kupietz9d599592022-05-01 16:29:18 +0200126 }
Marc Kupietz0a378672022-04-30 09:35:27 +0200127 SSLSocketFactory socketFactory = sslUtil.createSSLSocketFactory();
Marc Kupietz9d599592022-05-01 16:29:18 +0200128 lc = new LDAPConnection(socketFactory);
Marc Kupietz0a378672022-04-30 09:35:27 +0200129 } catch (GeneralSecurityException e) {
130 System.err.printf("Error: login: Connecting to LDAPS Server: failed: '%s'!\n", e);
Marc Kupietz75e78282022-05-02 20:39:20 +0200131 ldapTerminate(null);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200132 return new LdapAuth3Result(null, LDAP_AUTH_RCONNECT);
Marc Kupietz0a378672022-04-30 09:35:27 +0200133 }
134 } else {
135 lc = new LDAPConnection();
Marc Kupietz9d599592022-05-01 16:29:18 +0200136 }
137 try {
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200138 lc.connect(ldapConfig.host, ldapConfig.port);
139 if (DEBUGLOG && ldapConfig.useSSL) System.out.println("LDAPS Connection = OK\n");
140 if (DEBUGLOG && !ldapConfig.useSSL) System.out.println("LDAP Connection = OK\n");
Marc Kupietz9d599592022-05-01 16:29:18 +0200141 } catch (LDAPException e) {
142 String fullStackTrace = org.apache.commons.lang.exception.ExceptionUtils.getFullStackTrace(e);
143 System.err.printf("Error: login: Connecting to LDAP Server: failed: '%s'!\n", fullStackTrace);
Marc Kupietz75e78282022-05-02 20:39:20 +0200144 ldapTerminate(lc);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200145 return new LdapAuth3Result(null, LDAP_AUTH_RCONNECT);
Marc Kupietz0a378672022-04-30 09:35:27 +0200146 }
Marc Kupietz0a378672022-04-30 09:35:27 +0200147 if (DEBUGLOG) System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
Bodmo3d6bd352017-04-25 11:31:39 +0200148
Marc Kupietz0a378672022-04-30 09:35:27 +0200149 try {
150 // bind to server:
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200151 if (DEBUGLOG) System.out.printf("Binding with '%s' ...\n", ldapConfig.sLoginDN);
152 lc.bind(ldapConfig.sLoginDN, ldapConfig.sPwd);
Marc Kupietz9d599592022-05-01 16:29:18 +0200153 if (DEBUGLOG) System.out.print("Binding: OK.\n");
Marc Kupietz0a378672022-04-30 09:35:27 +0200154 } catch (LDAPException e) {
155 System.err.printf("Error: login: Binding failed: '%s'!\n", e);
Marc Kupietz75e78282022-05-02 20:39:20 +0200156 ldapTerminate(lc);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200157 return new LdapAuth3Result(null, LDAP_AUTH_RINTERR);
Marc Kupietz0a378672022-04-30 09:35:27 +0200158 }
Bodmo3d6bd352017-04-25 11:31:39 +0200159
Marc Kupietz0a378672022-04-30 09:35:27 +0200160 if (DEBUGLOG) System.out.printf("Debug: isConnected=%d\n", lc.isConnected() ? 1 : 0);
Bodmo3d6bd352017-04-25 11:31:39 +0200161
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200162 if (DEBUGLOG) System.out.printf("Finding user '%s'...\n", login);
Bodmo3d6bd352017-04-25 11:31:39 +0200163
Marc Kupietz7cb32132022-05-09 06:25:47 +0200164 SearchResult srchRes = null;
Marc Kupietz0a378672022-04-30 09:35:27 +0200165 try {
Marc Kupietz7cb32132022-05-09 06:25:47 +0200166 if (DEBUGLOG) System.out.printf("Searching with searchFilter: '%s'.\n", insensitiveSearchFilter);
Bodmo3d6bd352017-04-25 11:31:39 +0200167
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200168 srchRes = lc.search(ldapConfig.searchBase, SearchScope.SUB, searchFilterInstance);
Bodmo3d6bd352017-04-25 11:31:39 +0200169
Marc Kupietz7cb32132022-05-09 06:25:47 +0200170 if (DEBUGLOG) System.out.printf("Found '%s': %d entries.\n", login, srchRes.getEntryCount());
Marc Kupietz0a378672022-04-30 09:35:27 +0200171 } catch (LDAPSearchException e) {
Marc Kupietz30925d82022-05-06 15:33:52 +0200172 System.err.printf("Error: Search for User failed: '%s'!\n", e);
Marc Kupietz0a378672022-04-30 09:35:27 +0200173 }
Bodmo3d6bd352017-04-25 11:31:39 +0200174
Marc Kupietz30925d82022-05-06 15:33:52 +0200175 if (srchRes == null || srchRes.getEntryCount() == 0) {
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200176 if (DEBUGLOG) System.out.printf("Finding '%s': no entry found!\n", login);
Marc Kupietz30925d82022-05-06 15:33:52 +0200177 ldapTerminate(lc);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200178 return new LdapAuth3Result(null, LDAP_AUTH_RUNKNOWN);
Marc Kupietz0a378672022-04-30 09:35:27 +0200179 }
Bodmo3d6bd352017-04-25 11:31:39 +0200180
Marc Kupietz30925d82022-05-06 15:33:52 +0200181 if (bindWithFoundDN) {
182 String matchedDN = srchRes.getSearchEntries().get(0).getDN();
183 if (DEBUGLOG) System.out.printf("Requested bind for found user %s' failed.\n", matchedDN);
184 try {
185 // bind to server:
186 if (DEBUGLOG) System.out.printf("Binding with '%s' ...\n", matchedDN);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200187 BindResult bindResult = lc.bind(matchedDN, password);
Marc Kupietz30925d82022-05-06 15:33:52 +0200188 if (DEBUGLOG) System.out.print("Binding: OK.\n");
Marc Kupietz7cb32132022-05-09 06:25:47 +0200189 if (!bindResult.getResultCode().equals(ResultCode.SUCCESS)) {
190 ldapTerminate(lc);
191 return new LdapAuth3Result(null, LDAP_AUTH_RUNKNOWN);
192 }
Marc Kupietz30925d82022-05-06 15:33:52 +0200193 } catch (LDAPException e) {
194 System.err.printf("Error: login: Binding failed: '%s'!\n", e);
195 ldapTerminate(lc);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200196 return new LdapAuth3Result(null, LDAP_AUTH_RUNKNOWN);
197 }
198 }
199
200 if (applyExtraFilters) {
201 if (ldapConfig.authFilter != null && !ldapConfig.authFilter.isEmpty()) {
202 srchRes = applyAdditionalFilter(login, ldapConfig, ldapConfig.authFilter, searchFilterInstance, lc);
203 if (srchRes == null || srchRes.getEntryCount() == 0) {
204 ldapTerminate(lc);
205 return new LdapAuth3Result(null, LDAP_AUTH_RNOTREG);
206 }
207 }
208
209 if (ldapConfig.userNotBlockedFilter != null && !ldapConfig.userNotBlockedFilter.isEmpty()) {
210 srchRes = applyAdditionalFilter(login, ldapConfig, ldapConfig.userNotBlockedFilter, searchFilterInstance, lc);
211 if (srchRes == null || srchRes.getEntryCount() == 0) {
212 ldapTerminate(lc);
213 return new LdapAuth3Result(null, LDAP_AUTH_RLOCKED);
214 }
Marc Kupietz30925d82022-05-06 15:33:52 +0200215 }
216 }
217
Marc Kupietz75e78282022-05-02 20:39:20 +0200218 ldapTerminate(lc);
Marc Kupietz7cb32132022-05-09 06:25:47 +0200219 return new LdapAuth3Result(srchRes, LDAP_AUTH_ROK);
220 }
221
222 private static SearchResult applyAdditionalFilter(String login, LDAPConfig ldapConfig, String searchFilterInstance, String extraFilter, LDAPConnection lc) {
223 SearchResult srchRes;
224 srchRes = null;
225 try {
226 String combindedFilterInstance = "(&" + searchFilterInstance + extraFilter + ")";
227 if (DEBUGLOG) System.out.printf("Searching with additional Filter: '%s'.\n", extraFilter);
228 srchRes = lc.search(ldapConfig.searchBase, SearchScope.SUB, combindedFilterInstance);
229 if (DEBUGLOG) System.out.printf("Found '%s': %d entries.\n", login, srchRes.getEntryCount());
230 } catch (LDAPSearchException e) {
231 System.err.printf("Error: Search for User failed: '%s'!\n", e);
232 }
Marc Kupietz75e78282022-05-02 20:39:20 +0200233 return srchRes;
Marc Kupietz0a378672022-04-30 09:35:27 +0200234 }
Bodmo3d6bd352017-04-25 11:31:39 +0200235
Marc Kupietz75e78282022-05-02 20:39:20 +0200236 public static String getEmail(String sUserDN, String ldapConfigFilename) throws LDAPException {
237 String sUserPwd = "*";
Marc Kupietz9a1188e2022-05-05 23:26:14 +0200238 LDAPConfig ldapConfig = new LDAPConfig(ldapConfigFilename);
239 final String emailAttribute = ldapConfig.emailAttribute;
Marc Kupietz75e78282022-05-02 20:39:20 +0200240
Marc Kupietz7cb32132022-05-09 06:25:47 +0200241 SearchResult searchResult = search(sUserDN, sUserPwd, ldapConfig, false, false).getSearchResultValue();
Marc Kupietz75e78282022-05-02 20:39:20 +0200242
243 if (searchResult == null) {
244 return null;
245 }
246
247 for (SearchResultEntry entry : searchResult.getSearchEntries()) {
248 String mail = entry.getAttributeValue(emailAttribute);
249 if (mail != null) {
250 return mail;
251 }
252 }
253 return null;
254 }
margarethaaa87e202023-04-03 12:24:47 +0200255
256 public static String getUsername(String sUserDN, String ldapConfigFilename) throws LDAPException {
257 String sUserPwd = "*";
258 LDAPConfig ldapConfig = new LDAPConfig(ldapConfigFilename);
259 final String idsC2Attribute = "idsC2Profile";
260 final String uidAttribute = "uid";
261
262 SearchResult searchResult = search(sUserDN, sUserPwd, ldapConfig, false, false)
263 .getSearchResultValue();
264
265 if (searchResult == null) {
266 return null;
267 }
268
269 String username = null;
270 for (SearchResultEntry entry : searchResult.getSearchEntries()) {
271 username = entry.getAttributeValue(idsC2Attribute);
272 if (username == null) {
273 username = entry.getAttributeValue(uidAttribute);
274 }
275 }
276 return username;
277 }
Marc Kupietz75e78282022-05-02 20:39:20 +0200278
279 public static void ldapTerminate(LDAPConnection lc) {
Marc Kupietz0a378672022-04-30 09:35:27 +0200280 if (DEBUGLOG) System.out.println("Terminating...");
Bodmo3d6bd352017-04-25 11:31:39 +0200281
Marc Kupietz9d599592022-05-01 16:29:18 +0200282 if (lc != null) {
283 lc.close(null);
284 }
Marc Kupietz0a378672022-04-30 09:35:27 +0200285 if (DEBUGLOG) System.out.println("closing connection: done.\n");
Marc Kupietz0a378672022-04-30 09:35:27 +0200286 }
Bodmo3d6bd352017-04-25 11:31:39 +0200287
Marc Kupietz9d599592022-05-01 16:29:18 +0200288 private static void addSSLCipherSuites(String ciphersCsv) {
289 // add e.g. TLS_RSA_WITH_AES_256_GCM_SHA384
290 Set<String> ciphers = new HashSet<>();
291 ciphers.addAll(SSLUtil.getEnabledSSLCipherSuites());
292 ciphers.addAll(Arrays.asList(ciphersCsv.split(", *")));
293 SSLUtil.setEnabledSSLCipherSuites(ciphers);
294 }
295
Marc Kupietz0a378672022-04-30 09:35:27 +0200296 @Override
297 public TokenType getTokenType() {
298 return TokenType.API;
299 }
Bodmo3d6bd352017-04-25 11:31:39 +0200300
Marc Kupietz7cb32132022-05-09 06:25:47 +0200301 public static class LdapAuth3Result {
302 final int errorCode;
303 final Object value;
304
305
306 public LdapAuth3Result(Object value, int errorCode) {
307 this.errorCode = errorCode;
308 this.value = value;
309 }
310
311 public int getErrorCode() {
312 return errorCode;
313 }
314
315 public Object getValue() {
316 return value;
317 }
318
319 public SearchResult getSearchResultValue() {
320 return (SearchResult) value;
321 }
322 }
Bodmo3d6bd352017-04-25 11:31:39 +0200323}