/* * Copyright 2002-2005 the original author or authors. * * 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 info.jtrac.acegi; import info.jtrac.Jtrac; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import org.acegisecurity.Authentication; import org.acegisecurity.AuthenticationException; import org.acegisecurity.providers.AuthenticationProvider; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.acegisecurity.userdetails.UserDetails; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; /** * custom simple LDAP integration approach, where only authentication * is expected from LDAP, Space allocations have to be performed within JTrac only * * we are not using Acegi LDAP support because * a) it does not appear to support binding _as_ the user signing in * as opposed to a "hardcoded" user and password which is not very nice * b) easier to configure, customize and extend in the future */ public class JtracLdapAuthenticationProvider implements AuthenticationProvider, InitializingBean { private final Logger logger = LoggerFactory.getLogger(getClass()); private Jtrac jtrac; private String ldapUrl; private String activeDirectoryDomain; private String searchBase; private String searchKey; private String displayNameKey = "cn"; private String mailKey = "mail"; private String[] otherReturningAttributes; private String[] returningAttributes; // please refer http://forum.java.sun.com/thread.jspa?threadID=726601&tstart=0 // for the Active Directory LDAP Fast Bind Control approach used here private Control control = new Control() { public byte[] getEncodedValue() { return null; } public String getID() { return "1.2.840.113556.1.4.1781"; } public boolean isCritical() { return true; } }; public void setJtrac(Jtrac jtrac) { this.jtrac = jtrac; } public void setLdapUrl(String ldapUrl) { this.ldapUrl = ldapUrl; } public void setActiveDirectoryDomain(String activeDirectoryDomain) { this.activeDirectoryDomain = activeDirectoryDomain; } public void setSearchBase(String searchBase) { this.searchBase = searchBase; } public void setSearchKey(String searchKey) { this.searchKey = searchKey; } public void setDisplayNameKey(String displayNameKey) { this.displayNameKey = displayNameKey; } public void setMailKey(String mailKey) { this.mailKey = mailKey; } public void setOtherReturningAttributes(String[] otherReturningAttributes) { this.otherReturningAttributes = otherReturningAttributes; } public boolean supports(Class clazz) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(clazz); } public Authentication authenticate(Authentication authentication) { if (!supports(authentication.getClass())) { return null; } logger.debug("attempting authentication via LDAP"); Map<String, String> attributes = null; try { attributes = bind(authentication.getName(), authentication.getCredentials().toString()); } catch(Exception e) { logger.debug("bind failed: " + e); logger.debug("returning null from ldap authentication provider"); return null; } logger.debug("user details retrieved from LDAP, now checking local database"); UserDetails userDetails = null; try { userDetails = jtrac.loadUserByUsername(authentication.getName()); } catch(AuthenticationException ae) { // catch just to log, then re-throw as-is logger.debug("ldap user not allocated to any Spaces within JTrac"); throw ae; } return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } /** * displayName and mail are returned always, the map allows us to support * getting arbitrary properties in the future, hopefully */ public Map<String, String> bind(String loginName, String password) throws Exception { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, ldapUrl); env.put(Context.SECURITY_AUTHENTICATION, "simple"); LdapContext ctx = null; if(activeDirectoryDomain != null) { // we are using Active Directory Control[] controls = new Control[] {control}; ctx = new InitialLdapContext(env, controls); logger.debug("Active Directory LDAP context initialized"); ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, activeDirectoryDomain + "\\" + loginName); ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); // javax.naming.AuthenticationException ctx.reconnect(controls); logger.debug("Active Directory LDAP bind successful"); } else { // standard LDAP env.put(Context.SECURITY_PRINCIPAL, searchKey + "=" + loginName + "," + searchBase); env.put(Context.SECURITY_CREDENTIALS, password); ctx = new InitialLdapContext(env, null); logger.debug("Standard LDAP bind successful"); } SearchControls sc = new SearchControls(); sc.setSearchScope(SearchControls.SUBTREE_SCOPE); sc.setReturningAttributes(returningAttributes); NamingEnumeration results = ctx.search(searchBase, searchKey + "=" + loginName, sc); while(results.hasMoreElements()) { SearchResult sr = (SearchResult) results.next(); Attributes attrs = sr.getAttributes(); logger.debug("attributes: " + attrs); Map<String, String> map = new HashMap<String, String>(returningAttributes.length); for(String key : returningAttributes) { Attribute attr = attrs.get(key); if (attr != null) { map.put(key, (String) attr.get()); } } return map; // there should be only one anyway } // if we reached here, there was no search result throw new Exception("no results returned from ldap"); } // one-time init routine normally called by Spring as InitializingBean // but when we use a custom FactoryBean, we have to call this manually public void afterPropertiesSet() { if(otherReturningAttributes != null) { List<String> keys = new ArrayList<String>(); keys.add(mailKey); keys.add(displayNameKey); for(String s : otherReturningAttributes) { keys.add(s); } returningAttributes = keys.toArray(new String[keys.size()]); } else { returningAttributes = new String[] {mailKey, displayNameKey}; } if(searchKey == null) { if(activeDirectoryDomain != null && activeDirectoryDomain.trim().length() > 0) { searchKey = "sAMAccountName"; } else { activeDirectoryDomain = null; searchKey = "uid"; } } logger.info("ldap authenthication provider initialized searchKey = '" + searchKey + "'" + ", searchBase = '" + searchBase + "', activeDirectoryDomain = '" + activeDirectoryDomain + "'" + ", ldapUrl = '" + ldapUrl + "'"); } }