2015-11-19 14:39:21 -08:00
|
|
|
/**
|
|
|
|
* Copyright 2015 LinkedIn Corp. All rights reserved.
|
|
|
|
*
|
|
|
|
* Licensed 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.
|
|
|
|
*/
|
|
|
|
package security;
|
|
|
|
|
|
|
|
import dao.UserDAO;
|
2016-03-10 16:32:37 -08:00
|
|
|
import models.User;
|
2015-11-19 14:39:21 -08:00
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
|
import play.Logger;
|
|
|
|
import play.Play;
|
|
|
|
|
2016-03-10 16:32:37 -08:00
|
|
|
import java.sql.SQLException;
|
2015-11-19 14:39:21 -08:00
|
|
|
import java.util.Hashtable;
|
2016-03-10 16:32:37 -08:00
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
2015-11-19 14:39:21 -08:00
|
|
|
|
|
|
|
import javax.naming.Context;
|
|
|
|
import javax.naming.NamingException;
|
2016-03-10 16:32:37 -08:00
|
|
|
import javax.naming.NamingEnumeration;
|
|
|
|
import javax.naming.directory.Attribute;
|
|
|
|
import javax.naming.directory.Attributes;
|
|
|
|
import javax.naming.directory.BasicAttribute;
|
|
|
|
import javax.naming.directory.BasicAttributes;
|
2015-11-19 14:39:21 -08:00
|
|
|
import javax.naming.directory.DirContext;
|
|
|
|
import javax.naming.directory.InitialDirContext;
|
2016-03-10 16:32:37 -08:00
|
|
|
import javax.naming.directory.SearchResult;
|
|
|
|
|
2015-11-19 14:39:21 -08:00
|
|
|
|
|
|
|
public class AuthenticationManager {
|
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
public static String MASTER_LDAP_URL_KEY = "authentication.ldap.url";
|
|
|
|
public static String MASTER_PRINCIPAL_DOMAIN_KEY = "authentication.principal.domain";
|
|
|
|
public static String LDAP_CONTEXT_FACTORY_CLASS_KEY = "authentication.ldap.context_factory_class";
|
|
|
|
public static String LDAP_SEARCH_BASE_KEY = "authentication.ldap.search.base";
|
|
|
|
|
|
|
|
public static String LDAP_DISPLAY_NAME_KEY = "displayName";
|
|
|
|
public static String LDAP_MAIL_KEY = "mail";
|
|
|
|
public static String LDAP_DEPARTMENT_NUMBER_KEY = "departmentNumber";
|
|
|
|
|
|
|
|
public static void authenticateUser(String userName, String password)
|
|
|
|
throws NamingException, SQLException {
|
|
|
|
if (userName == null || userName.isEmpty() || password == null || password.isEmpty()) {
|
|
|
|
throw new IllegalArgumentException("Username and password can not be blank.");
|
|
|
|
}
|
2015-11-19 14:39:21 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
if (UserDAO.authenticate(userName, password)) {
|
|
|
|
UserDAO.insertLoginHistory(userName, "default", "SUCCESS", null);
|
|
|
|
return;
|
|
|
|
}
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
final String contextFactories = Play.application().configuration().getString(LDAP_CONTEXT_FACTORY_CLASS_KEY);
|
2016-10-13 15:22:56 -07:00
|
|
|
/* three LDAP properties, each is a '|' separated string of same number of tokens. e.g.
|
|
|
|
Url: "ldaps://ldap1.abc.com:1234|ldap://ldap2.abc.com:5678"
|
|
|
|
Principal Domain: "@abc.com|@abc.cn"
|
|
|
|
Search Base: "ou=Staff Users,dc=abc,dc=com|ou=Staff Users,dc=abc,dc=cn"
|
|
|
|
*/
|
2016-10-13 14:27:02 -07:00
|
|
|
final String[] ldapUrls = Play.application().configuration().getString(MASTER_LDAP_URL_KEY).split("\\s*\\|\\s*");
|
|
|
|
final String[] principalDomains =
|
|
|
|
Play.application().configuration().getString(MASTER_PRINCIPAL_DOMAIN_KEY).split("\\s*\\|\\s*");
|
|
|
|
final String[] ldapSearchBase =
|
|
|
|
Play.application().configuration().getString(LDAP_SEARCH_BASE_KEY).split("\\s*\\|\\s*");
|
|
|
|
|
|
|
|
DirContext ctx = null;
|
2016-10-19 17:08:07 -07:00
|
|
|
int i;
|
|
|
|
for (i = 0; i < ldapUrls.length; i++) {
|
2016-10-13 14:27:02 -07:00
|
|
|
try {
|
|
|
|
Hashtable<String, String> env =
|
|
|
|
buildEnvContext(userName, password, contextFactories, ldapUrls[i], principalDomains[i]);
|
|
|
|
ctx = new InitialDirContext(env);
|
|
|
|
if (!UserDAO.userExist(userName)) {
|
|
|
|
User user = getAttributes(ctx, ldapSearchBase[i], userName, principalDomains[i]);
|
|
|
|
UserDAO.addLdapUser(user);
|
2015-11-19 14:39:21 -08:00
|
|
|
}
|
2016-10-13 14:27:02 -07:00
|
|
|
break;
|
|
|
|
} catch (NamingException e) {
|
|
|
|
// Logger.error("Ldap authentication failed for user " + userName + " - " + principalDomains[i] + " - " + ldapUrls[i], e);
|
2016-10-19 17:08:07 -07:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
// if exhausted all ldap options and can't authenticate user
|
|
|
|
if (i >= ldapUrls.length - 1) {
|
2016-10-19 17:08:07 -07:00
|
|
|
UserDAO.insertLoginHistory(userName, "LDAP", "FAILURE", e.getMessage());
|
2016-10-13 14:27:02 -07:00
|
|
|
throw e;
|
2015-11-19 14:39:21 -08:00
|
|
|
}
|
2016-10-13 14:27:02 -07:00
|
|
|
} catch (SQLException e) {
|
|
|
|
// Logger.error("Ldap authentication SQL error for user: " + userName, e);
|
|
|
|
UserDAO.insertLoginHistory(userName, "LDAP", "FAILURE", ldapUrls[i] + e.getMessage());
|
|
|
|
throw e;
|
|
|
|
} finally {
|
|
|
|
if (ctx != null) {
|
|
|
|
ctx.close();
|
2015-11-19 14:39:21 -08:00
|
|
|
}
|
2016-10-13 14:27:02 -07:00
|
|
|
}
|
2015-11-19 14:39:21 -08:00
|
|
|
}
|
2016-10-19 17:08:07 -07:00
|
|
|
UserDAO.insertLoginHistory(userName, "LDAP", "SUCCESS", ldapUrls[i]);
|
2016-10-13 14:27:02 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private static Hashtable<String, String> buildEnvContext(String username, String password, String contextFactory,
|
|
|
|
String ldapUrl, String principalDomain) {
|
|
|
|
Hashtable<String, String> env = new Hashtable<>(11);
|
|
|
|
env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
|
|
|
|
env.put(Context.PROVIDER_URL, ldapUrl);
|
|
|
|
env.put(Context.SECURITY_PRINCIPAL, username + principalDomain);
|
|
|
|
env.put(Context.SECURITY_CREDENTIALS, password);
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Map<String, String> getUserAttributes(DirContext ctx, String searchBase, String userName,
|
|
|
|
String principalDomain, String... attributeNames)
|
|
|
|
throws NamingException {
|
|
|
|
if (StringUtils.isBlank(userName)) {
|
|
|
|
throw new IllegalArgumentException("Username and password can not be blank.");
|
2015-11-19 14:39:21 -08:00
|
|
|
}
|
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
if (attributeNames.length == 0) {
|
|
|
|
return Collections.emptyMap();
|
|
|
|
}
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
Attributes matchAttr = new BasicAttributes(true);
|
|
|
|
BasicAttribute basicAttr = new BasicAttribute("userPrincipalName", userName + principalDomain);
|
|
|
|
matchAttr.put(basicAttr);
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
NamingEnumeration<? extends SearchResult> searchResult = ctx.search(searchBase, matchAttr, attributeNames);
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
if (ctx != null) {
|
|
|
|
ctx.close();
|
|
|
|
}
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
Map<String, String> result = new HashMap<>();
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
if (searchResult.hasMore()) {
|
|
|
|
NamingEnumeration<? extends Attribute> attributes = searchResult.next().getAttributes().getAll();
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
while (attributes.hasMore()) {
|
|
|
|
Attribute attr = attributes.next();
|
|
|
|
String attrId = attr.getID();
|
|
|
|
String attrValue = (String) attr.get();
|
2016-03-10 16:32:37 -08:00
|
|
|
|
2016-10-13 14:27:02 -07:00
|
|
|
result.put(attrId, attrValue);
|
|
|
|
}
|
2016-03-10 16:32:37 -08:00
|
|
|
}
|
2016-10-13 14:27:02 -07:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static User getAttributes(DirContext ctx, String searchBase, String userName, String principalDomain)
|
|
|
|
throws NamingException, SQLException {
|
|
|
|
|
|
|
|
Map<String, String> userDetailMap =
|
|
|
|
getUserAttributes(ctx, searchBase, userName, principalDomain, LDAP_DISPLAY_NAME_KEY, LDAP_MAIL_KEY,
|
|
|
|
LDAP_DEPARTMENT_NUMBER_KEY);
|
|
|
|
|
|
|
|
String displayName = userDetailMap.get(LDAP_DISPLAY_NAME_KEY);
|
|
|
|
String[] displayNameTokens = displayName.trim().replaceAll(" +", " ").split(" ");
|
|
|
|
String firstName = displayNameTokens[0];
|
|
|
|
String lastName = displayNameTokens[1];
|
|
|
|
String email = userDetailMap.get(LDAP_MAIL_KEY);
|
|
|
|
String department = userDetailMap.get(LDAP_DEPARTMENT_NUMBER_KEY);
|
|
|
|
int departmentNum = 0;
|
|
|
|
if (StringUtils.isNotBlank(department)) {
|
|
|
|
try {
|
|
|
|
departmentNum = Integer.parseInt(department);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
Logger.error("Convert department number failed. Error message: " + e.getMessage());
|
|
|
|
departmentNum = 0;
|
|
|
|
}
|
2016-03-10 16:32:37 -08:00
|
|
|
}
|
2016-10-13 14:27:02 -07:00
|
|
|
User user = new User();
|
|
|
|
user.email = email;
|
|
|
|
user.userName = userName;
|
|
|
|
user.name = firstName + " " + lastName;
|
|
|
|
user.departmentNum = departmentNum;
|
|
|
|
return user;
|
|
|
|
}
|
|
|
|
}
|