All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.orientechnologies.security.ldap.OLDAPLibrary Maven / Gradle / Ivy

/**
 * Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *
 * 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.
 *
 * For more information: http://www.orientdb.com
 */
package com.orientechnologies.security.ldap;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.PrivilegedAction;

import java.util.Hashtable;
import java.util.List;

import javax.naming.*;
import javax.naming.directory.*; // Attribute, Attributes, DirContext
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.NamingEnumeration;
import javax.security.auth.Subject;

import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.security.OSecurityAuthenticator;

import com.orientechnologies.security.kerberos.OKerberosAuthenticator;

import com.orientechnologies.common.log.OLogManager;

/**
 * LDAP Library
 * 
 * @author S. Colin Leister
 * 
 */
public class OLDAPLibrary
{
	public static DirContext openContext(final OServer oServer, final String authentication, final List ldapServers, final boolean debug)
	{
		DirContext dc = null;

		// Set up environment for creating initial context
		Hashtable env = new Hashtable();
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
		env.put("com.sun.jndi.ldap.connect.timeout", "30000"); // in milliseconds

		for(OLDAPServer ldap : ldapServers)
		{
			try
			{
				String url = ldap.getURL();
			
				// If the LDAPServer info is marked as an alias, then the real hostname needs to be acquired.
				if(ldap.isAlias()) url = getRealURL(ldap, debug);
			
				// Must use fully qualified hostname
				env.put(Context.PROVIDER_URL, url);

				if(debug) OLogManager.instance().info(null, "OLDAPLibrary.openContext() Trying ProviderURL: " + url);

				if(authentication.equalsIgnoreCase("GSSAPI") || authentication.equalsIgnoreCase("Kerberos"))
				{
					dc = openKerberosContext(oServer, env);
				}
				else
				if(authentication.equalsIgnoreCase("Simple"))
				{
					dc = openSimpleContext(env, ldap);
				}

				if(dc != null) break;
			}
			catch(Exception ex)
			{
				OLogManager.instance().error(null, "OLDAPLibrary.openContext() Exception: ", ex);			
			}
		}

		return dc;
	}

	private static DirContext openSimpleContext(Hashtable env, OLDAPServer ldap)
	{
		env.put(Context.SECURITY_AUTHENTICATION, "simple");
		env.put(Context.SECURITY_PRINCIPAL, ldap.getPrincipal());
		env.put(Context.SECURITY_CREDENTIALS, ldap.getCredentials());

		try
		{
			return new InitialDirContext(env);
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.openSimpleContext() Exception: ", ex);
		}

		return null;
	}

	private static DirContext openKerberosContext(OServer oServer, Hashtable env)
	{
		// Request the use of the "GSSAPI" SASL mechanism
		// Authenticate by using already established Kerberos credentials
		env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");

		OSecurityAuthenticator authMethod = oServer.getSecurity().getAuthenticator("Kerberos");
  
		if(authMethod != null && authMethod instanceof OKerberosAuthenticator)
		{
			OKerberosAuthenticator ka = (OKerberosAuthenticator)authMethod;

			Subject subject = ka.getClientSubject();
   
			return Subject.doAs(subject, new PrivilegedAction()
			{
				public DirContext run()
				{
					try
					{
						// Create initial context
						return new InitialDirContext(env);
					}
					catch(Exception ex)
					{
						OLogManager.instance().error(null, "OLDAPLibrary.openKerberosContext() Exception: ", ex);
					}

					return null;
				}
			});
		}
		else
		{
			OLogManager.instance().error(null, "OLDAPLibrary.openKerberosContext() Invalid OSecurityAuthenticator", null);
		}

		return null;
	}
	/*
	public static DirContext openContext(final Subject subject, final List ldapServers, final boolean debug)
	{
		return Subject.doAs(subject, new PrivilegedAction()
		{
			public DirContext run()
			{
				DirContext dc = null;
				
				// Set up environment for creating initial context
				Hashtable env = new Hashtable();

				env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

				// Request the use of the "GSSAPI" SASL mechanism
				// Authenticate by using already established Kerberos credentials
				env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");

				for(OLDAPServer ldap : ldapServers)
				{
					try
					{
						String url = ldap.getURL();
					
						// If the LDAPServer info is marked as an alias, then the real hostname needs to be acquired.
						if(ldap.isAlias()) url = getRealURL(ldap, debug);
					
						// Must use fully qualified hostname
						env.put(Context.PROVIDER_URL, url);
	
						if(debug) OLogManager.instance().info(null, "OLDAPLibrary.openContext() Trying ProviderURL: " + url);
						
						// Create initial context
						dc = new InitialDirContext(env);
						break;
					}
					catch(Exception ex)
					{
						OLogManager.instance().error(null, "OLDAPLibrary.openContext() Exception: ", ex);
					}
				}

				return dc;
			}
		});
	}
*/
	// If the LDAPServer's isAlias() returns true, then the specified hostname is an alias, requiring a reverse
	// look-up of its IP address to resolve the real hostname to use.  This is often used with DNS round-robin.
	private static String getRealURL(OLDAPServer ldap, final boolean debug) throws UnknownHostException
	{
		String realURL = ldap.getURL();
		
		if(ldap.isAlias())
		{
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.getRealURL() Alias hostname = " + ldap.getHostname());
			
			// Get the returned IP address from the alias.
			// May throw an UnknownHostException		
			InetAddress ipAddress = InetAddress.getByName(ldap.getHostname());

			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.getRealURL() IP Address = " + ipAddress.getHostAddress());
			
			// Now that we have the IP address, use it to get the real hostname.
			// We create a new InetAddress object, because hostnames are cached.
			InetAddress realAddress = InetAddress.getByName(ipAddress.getHostAddress());
			
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.getRealURL() Real hostname = " + realAddress.getHostName());
			
			realURL = ldap.getURL(realAddress.getHostName());
			
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.getRealURL() Real URL = " + realURL);
		}
		
		return realURL;
	}

	public static void retrieveUsers(DirContext ctx, final String baseDN, final String filter, final List principalList, final boolean debug)
	{
		try
		{
			if(ctx != null)
			{
				// If we're just obtaining users matching a filterDN, switch to a SearchControl.
//				traverse(ctx, startingDN, filterDN, principalList, debug);

				SearchControls sctls = new SearchControls();
				sctls.setSearchScope(SearchControls.SUBTREE_SCOPE); // Recursive

				String[] attribFilter = {"userPrincipalName", "altSecurityIdentities"};
				sctls.setReturningAttributes(attribFilter);
				
				NamingEnumeration ne = ctx.search(baseDN, filter, sctls);  // "(userPrincipalName=*)"
				
				while(ne.hasMore())
				{
					SearchResult sr = (SearchResult)ne.next();
					
					addPrincipal(sr, principalList, debug);
				}
			}
			else
			{
				if(debug) OLogManager.instance().error(null, "OLDAPLibrary.retrieveUsers() DirContext is null", null);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.retrieveUsers() Exception: ", ex);
		}
	}

	private static void addPrincipal(SearchResult sr, List principalList, final boolean debug)
	{
		try
		{
			Attributes attrs = sr.getAttributes();
			
			if(attrs != null)
			{
/*				
				// userPrincipalName	
				String upn = getUserPrincipalName(attrs);
				
				if(debug) OLogManager.instance().info(null, "OLDAPLibrary.addPrincipal() userPrincipalName: " + upn);

				if(upn != null)
				{
					// Some UPNs, especially in 'altSecurityIdentities' will store the UPNs as such "Kerberos: [email protected]"
					upn = removeKerberos(upn, debug);
					
					principalList.add(upn); //upn.toLowerCase());
				}
	*/			
				fillAttributeList(attrs, "userPrincipalName", principalList, debug);
				
				fillAttributeList(attrs, "altSecurityIdentities", principalList, debug);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.addPrincipal() Exception: ", ex);
		}
	}

	private static void traverse(DirContext ctx, String startingDN, String memberOfFilter, List principalList, final boolean debug)
	{
		try
		{
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.traverse() startingDN: %s, memberOfFilter: %s", startingDN, memberOfFilter);
			
			Attributes attrs = ctx.getAttributes(startingDN);

			if(attrs != null)
			{
				if(debug) OLogManager.instance().info(null, "OLDAPLibrary.traverse() Found attributes for startingDN: %s", startingDN);

				Attribute member = attrs.get("member");
						
				if(member != null)
				{
					for(NamingEnumeration ae = member.getAll(); ae.hasMore();)
					{
						String path = (String)ae.next();
							
						findMembers(ctx, path, memberOfFilter, principalList, debug);
					}
				}
				else
				{
					if(debug) OLogManager.instance().info(null, "OLDAPLibrary.traverse() startingDN: %s has no \"member\" attributes.", startingDN);
				}
			}
			else
			{
				if(debug) OLogManager.instance().error(null,
						"OLDAPLibrary.traverse() Unable to find attributes for startingDN: %s", null, startingDN);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.traverse() Exception: ", ex);
		}
	}

	private static void findMembers(DirContext ctx, String startingDN, String memberOfFilter, List principalList, final boolean debug)
	{
		try
		{
			Attributes attrs = ctx.getAttributes(startingDN);
			
			if(attrs != null)
			{
				if(debug) OLogManager.instance().info(null, "OLDAPLibrary.findMembers() Found attributes for startingDN: %s", startingDN);
				
				if(isGroup(attrs))
				{
					if(debug) OLogManager.instance().info(null, "OLDAPLibrary.findMembers() Found group for startingDN: %s", startingDN);
					
					Attribute member = attrs.get("member");
					
					if(member != null)
					{
						for(NamingEnumeration ae = member.getAll(); ae.hasMore();)
						{
							String path = (String)ae.next();
							findMembers(ctx, path, memberOfFilter, principalList, debug);
						}
					}
				}
				else
				if(isUser(attrs))
				{
					if(debug) OLogManager.instance().info(null, "OLDAPLibrary.findMembers() Found user for startingDN: %s", startingDN);

					if(isMemberOf(attrs, memberOfFilter))
					{					
						// userPrincipalName	
						String upn = getUserPrincipalName(attrs);
						
						if(debug) OLogManager.instance().info(null, "OLDAPLibrary.findMembers() StartingDN: " + startingDN + ", userPrincipalName: " + upn);

						if(upn != null)
						{
							// Some UPNs, especially in 'altSecurityIdentities' will store the UPNs as such "Kerberos: [email protected]"
							upn = removeKerberos(upn, debug);
							principalList.add(upn); //upn.toLowerCase());
						}
						
						fillAttributeList(attrs, "altSecurityIdentities", principalList, debug);
					}
				}
			}
			else
			{
				OLogManager.instance().error(null,
						"OLDAPLibrary.findMembers() Unable to find attributes for startingDN: %s", null, startingDN);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.findMembers() Exception: ", ex);
		}
	}
	
	// Separates the distinguished name and returns the top-level name.
	private static String getName(final String dn)
	{
		String name = null;
		
		String names[] = dn.split(",");
		
		if(names.length >= 1)
		{
			// >= 4 because "CN=" is 3
			if(names[0].length() >= 4)
			{
				name = names[0].substring(3);
			}	
		}
		
		return name;
	}

	private static void fillAttributeList(Attributes attrs, String name, List list, final boolean debug)
	{
		try
		{
			Attribute attribute = attrs.get(name);
			
			if(attribute != null && attribute.size() > 0)
			{
				NamingEnumeration ne = attribute.getAll();
				while(ne.hasMore())
				{
					String value = (String)ne.next();
					
					// Some UPNs, especially in 'altSecurityIdentities' will store the UPNs as such "Kerberos: [email protected]"
					value = removeKerberos(value, debug);
					
					list.add(value); //value.toLowerCase());
				}
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary fillAttributeList(" + name + ")", ex);
		}
	}
	
	private static String getFirstValue(Attributes attrs, String name)
	{
		try
		{
			Attribute attribute = attrs.get(name);
			
			if(attribute != null && attribute.size() > 0)
			{
				return (String)attribute.get(0);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.getFirstValue(" + name + ") ", ex);
		}
		
		return null;
	}

	private static String getUserPrincipalName(Attributes attrs)
	{
		return getFirstValue(attrs, "userPrincipalName");
	}

	private static boolean isGroup(Attributes attrs)
	{
		String objCategoryDN = getFirstValue(attrs, "objectCategory");
		
		if(objCategoryDN != null)
		{
			String objCategory = getName(objCategoryDN);
			
			if(objCategory.equalsIgnoreCase("Group")) return true;
		}
		
		return false;
	}

	private static boolean isUser(Attributes attrs)
	{
		String objCategoryDN = getFirstValue(attrs, "objectCategory");
		
		if(objCategoryDN != null)
		{
			String objCategory = getName(objCategoryDN);
			
			if(objCategory.equalsIgnoreCase("User") || objCategory.equalsIgnoreCase("Person")) return true;
		}
		
		return false;
	}

	private static boolean isMemberOf(Attributes attrs, String memberOfFilter)
	{
		try
		{
			Attribute memberOfAttr = attrs.get("memberOf");
			
			if(memberOfAttr != null)
			{
				for(NamingEnumeration mo = memberOfAttr.getAll(); mo.hasMore();)
				{
					String value = (String)mo.next();
					
					if(value.equalsIgnoreCase(memberOfFilter))
					{
						return true;
					}
				}
			}
			else
			{
				OLogManager.instance().error(null, "OLDAPLibrary.isMemberOf() Has no 'memberOf' attribute.", null);
			}
		}
		catch(Exception ex)
		{
			OLogManager.instance().error(null, "OLDAPLibrary.isMemberOf()", ex);
		}
		
		return false;	
	}

	// Some UPNs, especially in 'altSecurityIdentities' will store the UPNs as such "Kerberos: [email protected]"
	private static String removeKerberos(String upn, final boolean debug)
	{
		if((upn.startsWith("kerberos:") || upn.startsWith("Kerberos:")) && upn.length() > 9)
		{
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.removeKerberos() upn before: %s", upn);

			upn = upn.substring(9);
			upn.trim();
			
			if(debug) OLogManager.instance().info(null, "OLDAPLibrary.removeKerberos() upn after: %s", upn);
		}
		
		return upn;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy