001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package info.jtrac.acegi;
018:
019: import info.jtrac.Jtrac;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022:
023: import java.util.Hashtable;
024: import java.util.List;
025: import java.util.Map;
026: import javax.naming.Context;
027: import javax.naming.NamingEnumeration;
028: import javax.naming.directory.Attribute;
029: import javax.naming.directory.Attributes;
030: import javax.naming.directory.SearchControls;
031: import javax.naming.directory.SearchResult;
032: import javax.naming.ldap.Control;
033: import javax.naming.ldap.InitialLdapContext;
034: import javax.naming.ldap.LdapContext;
035:
036: import org.acegisecurity.Authentication;
037: import org.acegisecurity.AuthenticationException;
038: import org.acegisecurity.providers.AuthenticationProvider;
039: import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
040: import org.acegisecurity.userdetails.UserDetails;
041: import org.slf4j.Logger;
042: import org.slf4j.LoggerFactory;
043: import org.springframework.beans.factory.InitializingBean;
044:
045: /**
046: * custom simple LDAP integration approach, where only authentication
047: * is expected from LDAP, Space allocations have to be performed within JTrac only
048: *
049: * we are not using Acegi LDAP support because
050: * a) it does not appear to support binding _as_ the user signing in
051: * as opposed to a "hardcoded" user and password which is not very nice
052: * b) easier to configure, customize and extend in the future
053: */
054: public class JtracLdapAuthenticationProvider implements
055: AuthenticationProvider, InitializingBean {
056:
057: private final Logger logger = LoggerFactory.getLogger(getClass());
058:
059: private Jtrac jtrac;
060: private String ldapUrl;
061: private String activeDirectoryDomain;
062: private String searchBase;
063: private String searchKey;
064: private String displayNameKey = "cn";
065: private String mailKey = "mail";
066: private String[] otherReturningAttributes;
067: private String[] returningAttributes;
068:
069: // please refer http://forum.java.sun.com/thread.jspa?threadID=726601&tstart=0
070: // for the Active Directory LDAP Fast Bind Control approach used here
071: private Control control = new Control() {
072: public byte[] getEncodedValue() {
073: return null;
074: }
075:
076: public String getID() {
077: return "1.2.840.113556.1.4.1781";
078: }
079:
080: public boolean isCritical() {
081: return true;
082: }
083: };
084:
085: public void setJtrac(Jtrac jtrac) {
086: this .jtrac = jtrac;
087: }
088:
089: public void setLdapUrl(String ldapUrl) {
090: this .ldapUrl = ldapUrl;
091: }
092:
093: public void setActiveDirectoryDomain(String activeDirectoryDomain) {
094: this .activeDirectoryDomain = activeDirectoryDomain;
095: }
096:
097: public void setSearchBase(String searchBase) {
098: this .searchBase = searchBase;
099: }
100:
101: public void setSearchKey(String searchKey) {
102: this .searchKey = searchKey;
103: }
104:
105: public void setDisplayNameKey(String displayNameKey) {
106: this .displayNameKey = displayNameKey;
107: }
108:
109: public void setMailKey(String mailKey) {
110: this .mailKey = mailKey;
111: }
112:
113: public void setOtherReturningAttributes(
114: String[] otherReturningAttributes) {
115: this .otherReturningAttributes = otherReturningAttributes;
116: }
117:
118: public boolean supports(Class clazz) {
119: return UsernamePasswordAuthenticationToken.class
120: .isAssignableFrom(clazz);
121: }
122:
123: public Authentication authenticate(Authentication authentication) {
124: if (!supports(authentication.getClass())) {
125: return null;
126: }
127: logger.debug("attempting authentication via LDAP");
128: Map<String, String> attributes = null;
129: try {
130: attributes = bind(authentication.getName(), authentication
131: .getCredentials().toString());
132: } catch (Exception e) {
133: logger.debug("bind failed: " + e);
134: logger
135: .debug("returning null from ldap authentication provider");
136: return null;
137: }
138: logger
139: .debug("user details retrieved from LDAP, now checking local database");
140: UserDetails userDetails = null;
141: try {
142: userDetails = jtrac.loadUserByUsername(authentication
143: .getName());
144: } catch (AuthenticationException ae) { // catch just to log, then re-throw as-is
145: logger
146: .debug("ldap user not allocated to any Spaces within JTrac");
147: throw ae;
148: }
149: return new UsernamePasswordAuthenticationToken(userDetails, "",
150: userDetails.getAuthorities());
151: }
152:
153: /**
154: * displayName and mail are returned always, the map allows us to support
155: * getting arbitrary properties in the future, hopefully
156: */
157: public Map<String, String> bind(String loginName, String password)
158: throws Exception {
159: Hashtable env = new Hashtable();
160: env.put(Context.INITIAL_CONTEXT_FACTORY,
161: "com.sun.jndi.ldap.LdapCtxFactory");
162: env.put(Context.PROVIDER_URL, ldapUrl);
163: env.put(Context.SECURITY_AUTHENTICATION, "simple");
164: LdapContext ctx = null;
165: if (activeDirectoryDomain != null) { // we are using Active Directory
166: Control[] controls = new Control[] { control };
167: ctx = new InitialLdapContext(env, controls);
168: logger.debug("Active Directory LDAP context initialized");
169: ctx.addToEnvironment(Context.SECURITY_PRINCIPAL,
170: activeDirectoryDomain + "\\" + loginName);
171: ctx
172: .addToEnvironment(Context.SECURITY_CREDENTIALS,
173: password);
174: // javax.naming.AuthenticationException
175: ctx.reconnect(controls);
176: logger.debug("Active Directory LDAP bind successful");
177: } else { // standard LDAP
178: env.put(Context.SECURITY_PRINCIPAL, searchKey + "="
179: + loginName + "," + searchBase);
180: env.put(Context.SECURITY_CREDENTIALS, password);
181: ctx = new InitialLdapContext(env, null);
182: logger.debug("Standard LDAP bind successful");
183: }
184: SearchControls sc = new SearchControls();
185: sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
186: sc.setReturningAttributes(returningAttributes);
187: NamingEnumeration results = ctx.search(searchBase, searchKey
188: + "=" + loginName, sc);
189: while (results.hasMoreElements()) {
190: SearchResult sr = (SearchResult) results.next();
191: Attributes attrs = sr.getAttributes();
192: logger.debug("attributes: " + attrs);
193: Map<String, String> map = new HashMap<String, String>(
194: returningAttributes.length);
195: for (String key : returningAttributes) {
196: Attribute attr = attrs.get(key);
197: if (attr != null) {
198: map.put(key, (String) attr.get());
199: }
200: }
201: return map; // there should be only one anyway
202: }
203: // if we reached here, there was no search result
204: throw new Exception("no results returned from ldap");
205: }
206:
207: // one-time init routine normally called by Spring as InitializingBean
208: // but when we use a custom FactoryBean, we have to call this manually
209: public void afterPropertiesSet() {
210: if (otherReturningAttributes != null) {
211: List<String> keys = new ArrayList<String>();
212: keys.add(mailKey);
213: keys.add(displayNameKey);
214: for (String s : otherReturningAttributes) {
215: keys.add(s);
216: }
217: returningAttributes = keys.toArray(new String[keys.size()]);
218: } else {
219: returningAttributes = new String[] { mailKey,
220: displayNameKey };
221: }
222: if (searchKey == null) {
223: if (activeDirectoryDomain != null
224: && activeDirectoryDomain.trim().length() > 0) {
225: searchKey = "sAMAccountName";
226: } else {
227: activeDirectoryDomain = null;
228: searchKey = "uid";
229: }
230: }
231: logger
232: .info("ldap authenthication provider initialized searchKey = '"
233: + searchKey
234: + "'"
235: + ", searchBase = '"
236: + searchBase
237: + "', activeDirectoryDomain = '"
238: + activeDirectoryDomain
239: + "'"
240: + ", ldapUrl = '"
241: + ldapUrl + "'");
242: }
243:
244: }
|