001 /**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one or more
004 * contributor license agreements. See the NOTICE file distributed with
005 * this work for additional information regarding copyright ownership.
006 * The ASF licenses this file to You under the Apache License, Version 2.0
007 * (the "License"); you may not use this file except in compliance with
008 * the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 package org.apache.activemq.jaas;
019
020 import java.io.IOException;
021 import java.text.MessageFormat;
022 import java.util.ArrayList;
023 import java.util.HashSet;
024 import java.util.Hashtable;
025 import java.util.Iterator;
026 import java.util.Map;
027 import java.util.Set;
028 import javax.naming.AuthenticationException;
029 import javax.naming.CommunicationException;
030 import javax.naming.Context;
031 import javax.naming.Name;
032 import javax.naming.NameParser;
033 import javax.naming.NamingEnumeration;
034 import javax.naming.NamingException;
035 import javax.naming.directory.Attribute;
036 import javax.naming.directory.Attributes;
037 import javax.naming.directory.DirContext;
038 import javax.naming.directory.InitialDirContext;
039 import javax.naming.directory.SearchControls;
040 import javax.naming.directory.SearchResult;
041 import javax.security.auth.Subject;
042 import javax.security.auth.callback.Callback;
043 import javax.security.auth.callback.CallbackHandler;
044 import javax.security.auth.callback.NameCallback;
045 import javax.security.auth.callback.PasswordCallback;
046 import javax.security.auth.callback.UnsupportedCallbackException;
047 import javax.security.auth.login.LoginException;
048 import javax.security.auth.login.FailedLoginException;
049 import javax.security.auth.spi.LoginModule;
050
051 import org.apache.commons.logging.Log;
052 import org.apache.commons.logging.LogFactory;
053
054
055 /**
056 * @version $Rev: $ $Date: $
057 */
058 public class LDAPLoginModule implements LoginModule {
059
060 private static Log log = LogFactory.getLog(LDAPLoginModule.class);
061
062 private Subject subject;
063 private CallbackHandler handler;
064
065 private static final String INITIAL_CONTEXT_FACTORY = "initialContextFactory";
066 private static final String CONNECTION_URL = "connectionURL";
067 private static final String CONNECTION_USERNAME = "connectionUsername";
068 private static final String CONNECTION_PASSWORD = "connectionPassword";
069 private static final String CONNECTION_PROTOCOL = "connectionProtocol";
070 private static final String AUTHENTICATION = "authentication";
071 private static final String USER_BASE = "userBase";
072 private static final String USER_SEARCH_MATCHING = "userSearchMatching";
073 private static final String USER_SEARCH_SUBTREE = "userSearchSubtree";
074 private static final String ROLE_BASE = "roleBase";
075 private static final String ROLE_NAME = "roleName";
076 private static final String ROLE_SEARCH_MATCHING = "roleSearchMatching";
077 private static final String ROLE_SEARCH_SUBTREE = "roleSearchSubtree";
078 private static final String USER_ROLE_NAME = "userRoleName";
079
080 private String initialContextFactory;
081 private String connectionURL;
082 private String connectionUsername;
083 private String connectionPassword;
084 private String connectionProtocol;
085 private String authentication;
086 private String userBase;
087 private String roleBase;
088 private String roleName;
089 private String userRoleName;
090
091 private String username;
092
093 protected DirContext context = null;
094
095 private MessageFormat userSearchMatchingFormat;
096 private MessageFormat roleSearchMatchingFormat;
097
098 private boolean userSearchSubtreeBool = false;
099 private boolean roleSearchSubtreeBool = false;
100
101 Set groups = new HashSet();
102
103 public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
104 this.subject = subject;
105 this.handler = callbackHandler;
106 initialContextFactory = (String) options.get(INITIAL_CONTEXT_FACTORY);
107 connectionURL = (String) options.get(CONNECTION_URL);
108 connectionUsername = (String) options.get(CONNECTION_USERNAME);
109 connectionPassword = (String) options.get(CONNECTION_PASSWORD);
110 connectionProtocol = (String) options.get(CONNECTION_PROTOCOL);
111 authentication = (String) options.get(AUTHENTICATION);
112 userBase = (String) options.get(USER_BASE);
113 String userSearchMatching = (String) options.get(USER_SEARCH_MATCHING);
114 String userSearchSubtree = (String) options.get(USER_SEARCH_SUBTREE);
115 roleBase = (String) options.get(ROLE_BASE);
116 roleName = (String) options.get(ROLE_NAME);
117 String roleSearchMatching = (String) options.get(ROLE_SEARCH_MATCHING);
118 String roleSearchSubtree = (String) options.get(ROLE_SEARCH_SUBTREE);
119 userRoleName = (String) options.get(USER_ROLE_NAME);
120 userSearchMatchingFormat = new MessageFormat(userSearchMatching);
121 roleSearchMatchingFormat = new MessageFormat(roleSearchMatching);
122 userSearchSubtreeBool = new Boolean(userSearchSubtree).booleanValue();
123 roleSearchSubtreeBool = new Boolean(roleSearchSubtree).booleanValue();
124 }
125
126 public boolean login() throws LoginException {
127 Callback[] callbacks = new Callback[2];
128
129 callbacks[0] = new NameCallback("User name");
130 callbacks[1] = new PasswordCallback("Password", false);
131 try {
132 handler.handle(callbacks);
133 } catch (IOException ioe) {
134 throw (LoginException) new LoginException().initCause(ioe);
135 } catch (UnsupportedCallbackException uce) {
136 throw (LoginException) new LoginException().initCause(uce);
137 }
138 username = ((NameCallback) callbacks[0]).getName();
139 String password = new String(((PasswordCallback) callbacks[1]).getPassword());
140
141 if (username == null || "".equals(username) || password == null || "".equals(password)) {
142 return false;
143 }
144
145 try {
146 boolean result = authenticate(username, password);
147 if(!result) {
148 throw new FailedLoginException();
149 } else {
150 return true;
151 }
152 } catch (Exception e) {
153 throw (LoginException) new LoginException("LDAP Error").initCause(e);
154 }
155 }
156
157 public boolean logout() throws LoginException {
158 username = null;
159 return true;
160 }
161
162 public boolean commit() throws LoginException {
163 Set principals = subject.getPrincipals();
164 principals.add(new UserPrincipal(username));
165 Iterator iter = groups.iterator();
166 while (iter.hasNext()) {
167 principals.add(iter.next());
168 }
169 return true;
170 }
171
172 public boolean abort() throws LoginException {
173 username = null;
174 return true;
175 }
176
177 protected void close(DirContext context) {
178 try {
179 context.close();
180 } catch (Exception e) {
181 log.error(e);
182 }
183 }
184
185 protected boolean authenticate(String username, String password) throws Exception {
186
187 DirContext context = null;
188 context = open();
189
190 try {
191
192 String filter = userSearchMatchingFormat.format(new String[]{username});
193 SearchControls constraints = new SearchControls();
194 if (userSearchSubtreeBool) {
195 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
196 } else {
197 constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
198 }
199
200 //setup attributes
201 ArrayList list = new ArrayList();
202 if (userRoleName != null) {
203 list.add(userRoleName);
204 }
205 String[] attribs = new String[list.size()];
206 list.toArray(attribs);
207 constraints.setReturningAttributes(attribs);
208
209
210 NamingEnumeration results = context.search(userBase, filter, constraints);
211
212 if (results == null || !results.hasMore()) {
213 return false;
214 }
215
216 SearchResult result = (SearchResult) results.next();
217
218 if (results.hasMore()) {
219 //ignore for now
220 }
221 NameParser parser = context.getNameParser("");
222 Name contextName = parser.parse(context.getNameInNamespace());
223 Name baseName = parser.parse(userBase);
224 Name entryName = parser.parse(result.getName());
225 Name name = contextName.addAll(baseName);
226 name = name.addAll(entryName);
227 String dn = name.toString();
228
229 Attributes attrs = result.getAttributes();
230 if (attrs == null) {
231 return false;
232 }
233 ArrayList roles = null;
234 if (userRoleName != null) {
235 roles = addAttributeValues(userRoleName, attrs, roles);
236 }
237
238 //check the credentials by binding to server
239 if (bindUser(context, dn, password)) {
240 //if authenticated add more roles
241 roles = getRoles(context, dn, username, roles);
242 for (int i = 0; i < roles.size(); i++) {
243 groups.add(new GroupPrincipal((String) roles.get(i)));
244 }
245 } else {
246 return false;
247 }
248 } catch (CommunicationException e) {
249
250 } catch (NamingException e) {
251 if (context != null) {
252 close(context);
253 }
254 return false;
255 }
256
257
258 return true;
259 }
260
261 protected ArrayList getRoles(DirContext context, String dn, String username, ArrayList currentRoles) throws NamingException {
262 ArrayList list = currentRoles;
263 if (list == null) {
264 list = new ArrayList();
265 }
266 if (roleName == null || "".equals(roleName)) {
267 return list;
268 }
269 String filter = roleSearchMatchingFormat.format(new String[]{doRFC2254Encoding(dn), username});
270
271 SearchControls constraints = new SearchControls();
272 if (roleSearchSubtreeBool) {
273 constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
274 } else {
275 constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
276 }
277 NamingEnumeration results =
278 context.search(roleBase, filter, constraints);
279 while (results.hasMore()) {
280 SearchResult result = (SearchResult) results.next();
281 Attributes attrs = result.getAttributes();
282 if (attrs == null) {
283 continue;
284 }
285 list = addAttributeValues(roleName, attrs, list);
286 }
287 return list;
288
289 }
290
291
292 protected String doRFC2254Encoding(String inputString) {
293 StringBuffer buf = new StringBuffer(inputString.length());
294 for (int i = 0; i < inputString.length(); i++) {
295 char c = inputString.charAt(i);
296 switch (c) {
297 case '\\':
298 buf.append("\\5c");
299 break;
300 case '*':
301 buf.append("\\2a");
302 break;
303 case '(':
304 buf.append("\\28");
305 break;
306 case ')':
307 buf.append("\\29");
308 break;
309 case '\0':
310 buf.append("\\00");
311 break;
312 default:
313 buf.append(c);
314 break;
315 }
316 }
317 return buf.toString();
318 }
319
320 protected boolean bindUser(DirContext context, String dn, String password) throws NamingException {
321 boolean isValid = false;
322
323 context.addToEnvironment(Context.SECURITY_PRINCIPAL, dn);
324 context.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
325 try {
326 context.getAttributes("", null);
327 isValid = true;
328 } catch (AuthenticationException e) {
329 isValid = false;
330 log.debug("Authentication failed for dn=" + dn);
331 }
332
333 if (connectionUsername != null) {
334 context.addToEnvironment(Context.SECURITY_PRINCIPAL,
335 connectionUsername);
336 } else {
337 context.removeFromEnvironment(Context.SECURITY_PRINCIPAL);
338 }
339
340 if (connectionPassword != null) {
341 context.addToEnvironment(Context.SECURITY_CREDENTIALS,
342 connectionPassword);
343 } else {
344 context.removeFromEnvironment(Context.SECURITY_CREDENTIALS);
345 }
346
347 return isValid;
348 }
349
350 private ArrayList addAttributeValues(String attrId, Attributes attrs, ArrayList values)
351 throws NamingException
352 {
353
354 if (attrId == null || attrs == null) {
355 return values;
356 }
357 if (values == null) {
358 values = new ArrayList();
359 }
360 Attribute attr = attrs.get(attrId);
361 if (attr == null) {
362 return (values);
363 }
364 NamingEnumeration e = attr.getAll();
365 while (e.hasMore()) {
366 String value = (String) e.next();
367 values.add(value);
368 }
369 return values;
370 }
371
372 protected DirContext open() throws NamingException {
373 if (context != null) {
374 return context;
375 }
376
377 try {
378 Hashtable env = new Hashtable();
379 env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
380 if (connectionUsername != null || !"".equals(connectionUsername)) {
381 env.put(Context.SECURITY_PRINCIPAL, connectionUsername);
382 }
383 if (connectionPassword != null || !"".equals(connectionPassword)) {
384 env.put(Context.SECURITY_CREDENTIALS, connectionPassword);
385 }
386 env.put(Context.SECURITY_PROTOCOL, connectionProtocol);
387 env.put(Context.PROVIDER_URL, connectionURL);
388 env.put(Context.SECURITY_AUTHENTICATION, authentication);
389 context = new InitialDirContext(env);
390
391 } catch (NamingException e) {
392 log.error(e);
393 throw e;
394 }
395 return context;
396 }
397
398 }