/******************************************************************************
* Copyright (c) 2014-2015 VMware, Inc. 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*****************************************************************************/
package com.vmware.bdd.usermgmt;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.AuthenticationException;
import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchResult;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.vmware.bdd.apitypes.UserMgmtServer;
import com.vmware.bdd.exception.ValidationException;
import com.vmware.bdd.security.tls.SimpleServerTrustManager;
import com.vmware.bdd.security.tls.SimpleSeverTrustTlsSocketFactory;
import com.vmware.bdd.security.tls.TlsConnectionException;
import com.vmware.bdd.security.tls.TlsTcpClient;
import com.vmware.bdd.validation.ValidationError;
import com.vmware.bdd.validation.ValidationErrors;
/**
* Created By xiaoliangl on 11/27/14.
*/
@Component
public class UserMgmtServerValidService {
private final static Logger LOGGER = Logger.getLogger(UserMgmtServerValidService.class);
private static final String LDAP_GROUP_OBJECT_CLASS = "ldap_group_object_class";
private static final String LDAP_USER_OBJECT_CLASS = "ldap_user_object_class";
@Autowired
private LdapsTrustStoreConfig ldapsTrustStoreConfig;
@Autowired
private SssdConfigurationGenerator sssdConfigurationGenerator;
public void validateServerInfo(UserMgmtServer userMgmtServer, boolean forceTrustCert) {
String[] ldapUrlElements = getLdapProtocol(userMgmtServer.getPrimaryUrl());
boolean isLdaps = "LDAPS".equalsIgnoreCase(ldapUrlElements[0]);
if (isLdaps) {
validateCertificate(ldapUrlElements, forceTrustCert);
}
searchGroupDn(userMgmtServer, userMgmtServer.getMgmtVMUserGroupDn(), isLdaps);
}
private String[] getLdapProtocol(String ldapUrl) {
Pattern pattern = Pattern.compile("^(ldap(?:s?))\\:\\/\\/([-.\\w]*)(?:\\:([0-9]*))?(\\/.*)?$");
Matcher matcher = pattern.matcher(ldapUrl);
if (matcher.matches()) {
return new String[]{matcher.group(1), matcher.group(2), matcher.group(3)};
}
return null;
}
public void validateGroupUsers(UserMgmtServer userMgmtServer, Map<String, Set<String>> groupUsers) {
searchGroupAndUser(userMgmtServer, groupUsers);
}
public void validateCertificate(String[] ldapUrlElements, boolean forceTrustCert) {
if ("LDAPS".equalsIgnoreCase(ldapUrlElements[0])) {
String host = ldapUrlElements[1];
String port = ldapUrlElements[2];
int portNum = port == null ? 636 : Integer.parseInt(port);
TlsTcpClient tlsTcpClient = new TlsTcpClient();
SimpleServerTrustManager simpleServerTrustManager = new SimpleServerTrustManager();
simpleServerTrustManager.setTrustStoreConfig(ldapsTrustStoreConfig);
tlsTcpClient.setTrustManager(simpleServerTrustManager);
try {
tlsTcpClient.checkCertificateFirstly(host, portNum, forceTrustCert);
} catch (TlsConnectionException tlse) {
ValidationError error = new ValidationError("PrimaryUrl.CannotConnect", "Can not connect to the primary URL.");
ValidationErrors errors = new ValidationErrors();
errors.addError("PrimaryUrl", error);
throw new ValidationException(errors.getErrors());
}
}
}
public void searchGroup(UserMgmtServer userMgmtServer, String[] groupNames) {
Map<String, Set<String>> groupUsers = new HashMap<>();
for (String groupName: groupNames) {
groupUsers.put(groupName, null);
}
searchGroupAndUser(userMgmtServer, groupUsers);
}
//groupUsers is a groupName to group users map. For group without user, the String[] can be null or empty
public void searchGroupAndUser(UserMgmtServer userMgmtServer, Map<String, Set<String>> groupUsers) {
String[] groupNames = new String[groupUsers.keySet().size()];
groupUsers.keySet().toArray(groupNames);
String[] ldapUrlElements = getLdapProtocol(userMgmtServer.getPrimaryUrl());
boolean isLdaps = "LDAPS".equalsIgnoreCase(ldapUrlElements[0]);
Hashtable<String, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// Specify LDAPS URL
env.put(Context.PROVIDER_URL, userMgmtServer.getPrimaryUrl());
// Authenticate as Simple username and password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userMgmtServer.getUserName());
env.put(Context.SECURITY_CREDENTIALS, userMgmtServer.getPassword());
if (isLdaps) {
//@TODO it's interesting to add a custom socket factory to set SO_TIMEOUT
SimpleSeverTrustTlsSocketFactory.init(ldapsTrustStoreConfig);
env.put("java.naming.ldap.factory.socket", "com.vmware.bdd.security.tls.SimpleSeverTrustTlsSocketFactory");
}
// env.put(Context.SECURITY_PROTOCOL, "ssl");
DirContext ctx = null;
ValidationErrors validationErrors = new ValidationErrors();
String groupObjectClass = sssdConfigurationGenerator.get(userMgmtServer.getType()).get(LDAP_GROUP_OBJECT_CLASS);
String userObjectClass = sssdConfigurationGenerator.get(userMgmtServer.getType()).get(LDAP_USER_OBJECT_CLASS);
try {
// Create the initial context
ctx = new InitialDirContext(env);
NamingEnumeration<SearchResult> answer = null;
//validateServerInfo mgmt server default admin group
for(String groupName : groupNames) {
try {
answer = ctx.search(
userMgmtServer.getBaseGroupDn(),
"(&(objectClass={0}) (cn={1}))",
new Object[]{groupObjectClass, groupName}, null);
if (!answer.hasMoreElements()) {
validationErrors.addError(groupName, new ValidationError("GROUP.NOT_FOUND", String.format("Group (%1s) not found.", groupName)));
}
Set<String> users = groupUsers.get(groupName);
if (users != null && !users.isEmpty()) {
for (String user: users) {
String memberOf = "cn=" + groupName + "," + userMgmtServer.getBaseGroupDn();
answer = ctx.search(
userMgmtServer.getBaseUserDn(),
"(&(objectClass={0}) (cn={1}) (memberOf={2}))",
new Object[]{userObjectClass, user, memberOf}, null);
if (!answer.hasMoreElements()) {
validationErrors.addError(user, new ValidationError("USER.NOT_FOUND", String.format("User (%1s) not found in group (%2s).", user, groupName)));
}
}
}
} catch (NameNotFoundException nnf) {
validationErrors.addError("BaseGroupDn", new ValidationError("BASE_GROUP_DN.NOT_FOUND", "BaseGroupDn not found."));
} catch (InvalidNameException ine) {
validationErrors.addError("BaseGroupDn", new ValidationError("BASE_GROUP_DN.INVALID_DN", "BaseGroupDn is not a valid DN."));
}
}
} catch (AuthenticationException e) {
validationErrors.addError("UserCredential", new ValidationError("UserCredential.Invalid", "invalid username or password."));
} catch (InvalidNameException ine) {
validationErrors.addError("UserName", new ValidationError("USERNAME.INVALID_DN", "UserName is not a valid DN."));
} catch (CommunicationException ce) {
LOGGER.warn(ce.getMessage());
LOGGER.warn(ce.getCause().getMessage());
ce.printStackTrace();
validationErrors.addError("PrimaryUrl", new ValidationError("PrimaryUrl.CannotConnect", "Can not connect to the primary URL."));
} catch (NamingException e) {
throw new UserMgmtServerValidException(e);
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
//
}
}
if (!validationErrors.getErrors().isEmpty()) {
LOGGER.error(validationErrors.getErrors());
throw new ValidationException(validationErrors.getErrors());
}
}
}
protected void searchGroupDn(UserMgmtServer userMgmtServer, String groupDn, boolean isLdaps) {
Hashtable<String, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
// Specify LDAPS URL
env.put(Context.PROVIDER_URL, userMgmtServer.getPrimaryUrl());
// Authenticate as Simple username and password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userMgmtServer.getUserName());
env.put(Context.SECURITY_CREDENTIALS, userMgmtServer.getPassword());
if (isLdaps) {
//@TODO it's interesting to add a custom socket factory to set SO_TIMEOUT
SimpleSeverTrustTlsSocketFactory.init(ldapsTrustStoreConfig);
env.put("java.naming.ldap.factory.socket", "com.vmware.bdd.security.tls.SimpleSeverTrustTlsSocketFactory");
}
// env.put(Context.SECURITY_PROTOCOL, "ssl");
String groupObjectClass = sssdConfigurationGenerator.get(userMgmtServer.getType()).get(LDAP_GROUP_OBJECT_CLASS);
DirContext ctx = null;
ValidationErrors validationErrors = new ValidationErrors();
try {
// Create the initial context
ctx = new InitialDirContext(env);
NamingEnumeration<SearchResult> answer = null;
//validateServerInfo mgmt server default admin group
try {
answer = ctx.search(userMgmtServer.getBaseGroupDn(), null);
// if (!answer.hasMoreElements()) {
// validationErrors.addError("BaseGroupDn", new ValidationError("BASE_GROUP_DN.NOT_FOUND", "BaseGroupDn not found."));
// } else {
String groupCn = getCnFromGroupDn(groupDn);
if (groupCn == null || groupCn.trim().length() == 0) {
validationErrors.addError("AdminGroup", new ValidationError("ADMIN_GROUP.FORMAT_ERR", "CN attribute missing."));
} else {
answer = ctx.search(
userMgmtServer.getBaseGroupDn(),
"(&(objectClass={0}) (cn={1}))",
new Object[]{groupObjectClass, groupCn}, null);
if (!answer.hasMoreElements()) {
validationErrors.addError("AdminGroup", new ValidationError("ADMIN_GROUP.NOT_FOUND", "Admin Group not found."));
}
}
// }
} catch (NameNotFoundException nnf) {
validationErrors.addError("BaseGroupDn", new ValidationError("BASE_GROUP_DN.NOT_FOUND", "BaseGroupDn not found."));
} catch (InvalidNameException ine) {
validationErrors.addError("BaseGroupDn", new ValidationError("BASE_GROUP_DN.INVALID_DN", "BaseGroupDn is not a valid DN."));
}
try {
answer = ctx.search(userMgmtServer.getBaseUserDn(), null);
// if (!answer.hasMoreElements()) {
// validationErrors.addError("BaseUserDn", new ValidationError("BASE_USER_DN.NOT_FOUND", "BaseUserDn not found."));
// }
} catch (NameNotFoundException nnf) {
validationErrors.addError("BaseUserDn", new ValidationError("BASE_USER_DN.NOT_FOUND", "BaseUserDn not found."));
} catch (InvalidNameException ine) {
validationErrors.addError("BaseUserDn", new ValidationError("BASE_USER_DN.INVALID_DN", "BaseUserDn is not a valid DN."));
}
} catch (AuthenticationException e) {
validationErrors.addError("UserCredential", new ValidationError("UserCredential.Invalid", "invalid username or password."));
} catch (InvalidNameException ine) {
validationErrors.addError("UserName", new ValidationError("USERNAME.INVALID_DN", "UserName is not a valid DN."));
} catch (CommunicationException ce) {
LOGGER.warn(ce.getMessage());
LOGGER.warn(ce.getCause().getMessage());
ce.printStackTrace();
validationErrors.addError("PrimaryUrl", new ValidationError("PrimaryUrl.CannotConnect", "Can not connect to the primary URL."));
} catch (NamingException e) {
throw new UserMgmtServerValidException(e);
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
//
}
}
if (!validationErrors.getErrors().isEmpty()) {
throw new ValidationException(validationErrors.getErrors());
}
}
}
public String getCnFromGroupDn(String groupDn) {
String cn = null;
int index1 = groupDn.indexOf("cn=");
if (index1 == -1) {
index1 = groupDn.indexOf("CN=");
}
if (index1 != -1) {
int index2 = groupDn.indexOf(',', index1);
if (index2 != -1) {
cn = groupDn.substring(index1 + 3, index2);
}
}
return cn;
}
}