001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.services.security.auth.jaas;
019:
020: import java.io.IOException;
021: import java.security.Principal;
022: import java.util.HashSet;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import javax.security.auth.Subject;
027: import javax.security.auth.callback.Callback;
028: import javax.security.auth.callback.CallbackHandler;
029: import javax.security.auth.callback.NameCallback;
030: import javax.security.auth.callback.PasswordCallback;
031: import javax.security.auth.callback.UnsupportedCallbackException;
032: import javax.security.auth.login.LoginException;
033: import javax.security.auth.spi.LoginModule;
034:
035: import org.sape.carbon.core.component.Lookup;
036: import org.sape.carbon.core.exception.ExceptionUtility;
037:
038: import org.sape.carbon.services.security.management.SecurityManagementDataStoreException;
039: import org.sape.carbon.services.security.management.UnknownPrincipalException;
040: import org.sape.carbon.services.security.management.UserManager;
041:
042: /**
043: * JAAS Login Module for Carbon User Manager service.
044: *
045: * <p>
046: * The basic implementation here generates a <code>NameCallback</code> and
047: * <code>PasswordCallback</code> and uses them to retreive the username
048: * and String based password of the user. Credential is mostly handled
049: * as an <code>Object</code> reference, and hence adjusting the
050: * credential to be something other than a String is possible.
051: * </p>
052: *
053: * @author $Author: dvoet $ $Date: 2003/10/28 19:02:00 $
054: * @version $Revision: 1.7 $
055: *
056: * @since carbon 1.2
057: */
058: public class JaasCarbonLoginModule implements LoginModule {
059: /**
060: * Name of the key expected to hold the location of the UserManager
061: * service that will provide back end logic for the Carbon Login
062: * Module.
063: */
064: public static final String USERMANAGER_COMPONENT_KEY = "USERMANAGER_COMPONENT_KEY";
065:
066: /** Hold the UserManager providing backend logic. */
067: protected UserManager userManager;
068:
069: /** subject for this login. */
070: protected Subject subject;
071:
072: /** Handles retreiving the Username and Credential. */
073: protected CallbackHandler callbackHandler;
074:
075: /** Flag indicating if login has succeeded. */
076: protected boolean authenticated = false;
077:
078: /** Flag indicating if principals have been added */
079: protected boolean principalsInSubject;
080:
081: /** List of principals added to the subject. */
082: protected Set principalsForSubject = new HashSet();
083:
084: /**
085: * Initialize the module by storing all state internally and getting a
086: * reference to the UserManager object.
087: *
088: * @param subject the Subject to be authenticated.
089: * @param callbackHandler a CallbackHandler for communicating with the
090: * end user (prompting for usernames and passwords, for
091: * example).
092: * @param sharedState state shared with other configured LoginModules.
093: * @param options options specified in the login Configuration for
094: * this particular LoginModule.
095: */
096: public void initialize(Subject subject,
097: CallbackHandler callbackHandler, Map sharedState,
098: Map options) {
099: // only called (once!) after the constructor and before login
100: this .subject = subject;
101: this .callbackHandler = callbackHandler;
102: this .userManager = (UserManager) Lookup
103: .getInstance()
104: .fetchComponent(
105: (String) options
106: .get(JaasCarbonLoginModule.USERMANAGER_COMPONENT_KEY));
107: }
108:
109: /**
110: * Builds the callbacks, executes against them, gets username/credential,
111: * validates against Carbon UserManager service.
112: *
113: * @return if the user has been authenticated by this module
114: * @throws LoginException indicates an error in the login process
115: */
116: public boolean login() throws LoginException {
117: Callback[] callbacks = buildCallbacks();
118: executeCallbacks(callbacks);
119:
120: String username = getUsername(callbacks);
121: Object credential = getCredential(callbacks);
122:
123: try {
124: authenticated = userManager.authenticate(username,
125: credential);
126: } catch (SecurityManagementDataStoreException smdse) {
127: throw new LoginException(
128: "Caught SecurityManagementDataStoreException authenticating "
129: + "user: "
130: + ExceptionUtility
131: .printStackTracesToString(smdse));
132: }
133:
134: if (authenticated) {
135: addMainPrincipal(username);
136: addContainingGroups(username);
137: }
138:
139: return authenticated;
140: }
141:
142: /**
143: * Commit this modules login by adding principals to the
144: * subject.
145: * <p>
146: * This method adds in all principals withing the
147: * <code>principalsForSubject</code> member variable.
148: * </p>
149: *
150: * @return always return true
151: * @throws LoginException indicates an error committing the login
152: */
153: public boolean commit() throws LoginException {
154: boolean result = false;
155:
156: if (authenticated) {
157: subject.getPrincipals().addAll(principalsForSubject);
158: principalsInSubject = true;
159: result = true;
160: }
161:
162: return result;
163: }
164:
165: /**
166: * Abort the login.
167: * <p>
168: * If the Principals inside the <code>principalsForSubject</code>
169: * member variable have already been added, they will be removed
170: * from the subject's principal list.
171: * </p>
172: *
173: * @return always return true
174: * @throws LoginException indicates an error aborting the login
175: */
176: public boolean abort() throws LoginException {
177: if (principalsInSubject) {
178: subject.getPrincipals().removeAll(principalsForSubject);
179: principalsInSubject = false;
180: }
181:
182: return true;
183: }
184:
185: /**
186: * Empty implementation that always returns true.
187: *
188: * @return always returns true
189: * @throws LoginException indicates a failure logging the user out
190: * of the system.
191: */
192: public boolean logout() throws LoginException {
193: return true;
194: }
195:
196: /**
197: * Internal method to build the list of callbacks that are used.
198: * <p>
199: * The default implementation will create a <code>NameCallback</code>
200: * and <code>PasswordCallback</code> and place them into the
201: * array of callbacks.
202: * </p>
203: *
204: * @return array of callbacks to be executed by the handler
205: * @throws LoginException indicates an error building the callbacks.
206: * @see javax.security.auth.callback.NameCallback
207: * @see javax.security.auth.callback.PasswordCallback
208: */
209: protected Callback[] buildCallbacks() throws LoginException {
210: if (callbackHandler == null) {
211: throw new LoginException("No CallbackHandler Specified");
212: }
213:
214: Callback[] callbacks = new Callback[2];
215:
216: // add in the user name callback
217: callbacks[0] = new NameCallback(getUsernamePrompt());
218:
219: // add in the name callback with echo-on set to false
220: callbacks[1] = new PasswordCallback(getCredentialPrompt(),
221: false);
222:
223: return callbacks;
224: }
225:
226: /**
227: * Executes all callbacks in the list by telling the
228: * <code>CallbackHandler</code> to handle them.
229: *
230: * @param callbacks array of callbacks to be handled
231: * @throws LoginException indicates an error executing the callbacks
232: */
233: protected void executeCallbacks(Callback[] callbacks)
234: throws LoginException {
235: // Call the callback handler, who in turn, calls back to the
236: // callback objects, handing them the user name and credential.
237: // These callback objects hold onto the user name and credential.
238: // The login module retrieves the user name and credential
239: // from them later.
240: try {
241: callbackHandler.handle(callbacks);
242: } catch (IOException ioe) {
243: throw new LoginException(
244: "Error communicating with the user when executing callbacks. "
245: + "Caused by ["
246: + ExceptionUtility
247: .printStackTracesToString(ioe)
248: + "]");
249:
250: } catch (UnsupportedCallbackException uce) {
251: throw new LoginException(
252: "Error executing unsupported callback. "
253: + "Caused by ["
254: + ExceptionUtility
255: .printStackTracesToString(uce)
256: + "]");
257:
258: }
259:
260: }
261:
262: /**
263: * Get the username from the array of callbacks.
264: * <p>
265: * The default implementation iterates through the list of
266: * callbacks until the first <code>NameCallback</code> object
267: * gives a non-nullname.
268: * </p>
269: *
270: * @param callbacks list of callbacks to get a name from
271: * @return the first username encountered or null if none are found
272: */
273: protected String getUsername(Callback[] callbacks) {
274: String username = null;
275:
276: // Iterate over the callbacks until the first username
277: // call back and retreive it.
278: for (int i = 0; (i < callbacks.length) && (username == null); i++) {
279: if (callbacks[i] instanceof NameCallback) {
280: username = ((NameCallback) callbacks[i]).getName();
281: }
282: }
283:
284: return username;
285: }
286:
287: /**
288: * Get the credential from the array of callbacks.
289: * <p>
290: * The default implementation iterates through the list of
291: * callbacks until the first <code>PasswordCallback</code> object
292: * gives a non-null password. It then converts the char[] result
293: * from the callback into a string a returns it.
294: * </p>
295: *
296: * @param callbacks list of callbacks to get a name from
297: * @return the first password encountered or null if none are found
298: */
299: protected Object getCredential(Callback[] callbacks) {
300: char[] passwordArray = null;
301: String password = null;
302:
303: // Iterate over the callbacks until the first username
304: // call back and retreive it.
305: for (int i = 0; (i < callbacks.length)
306: && (passwordArray == null); i++) {
307: if (callbacks[i] instanceof PasswordCallback) {
308: passwordArray = ((PasswordCallback) callbacks[i])
309: .getPassword();
310: }
311: }
312:
313: if (passwordArray != null) {
314: password = new String(passwordArray);
315: }
316:
317: return password;
318: }
319:
320: /**
321: * Adds the main Principal object to <code>principalsForSubject</code>
322: * object.
323: * <p>
324: * The default will add the Carbon implementation of the user object.
325: * If a specific type of user object must be supplied, this method
326: * can be overriden.
327: * </p>
328: *
329: * @param username the name of the user to add the main principal for
330: */
331: protected void addMainPrincipal(String username)
332: throws LoginException {
333: try {
334: principalsForSubject
335: .add(userManager.retreiveUser(username));
336: } catch (SecurityManagementDataStoreException smdse) {
337: throw new LoginException(
338: "Caught SecurityManagementDataStoreException retrieving user: "
339: + ExceptionUtility
340: .printStackTracesToString(smdse));
341: }
342: }
343:
344: /**
345: * Adds all groups containing the given username.
346: * <p>
347: * The default will add the Carbon implementation of the group object.
348: * If a specific type of group object must be supplied, this method
349: * can be overriden.
350: * </p>
351: *
352: * @param username the name of the user to add the containing groups
353: * @throws LoginException indicates an error retreiving groups for
354: * a user.
355: */
356: protected void addContainingGroups(String username)
357: throws LoginException {
358:
359: try {
360: Principal user = userManager.retreiveUser(username);
361:
362: principalsForSubject.addAll(userManager
363: .retreiveGroups(user));
364: } catch (SecurityManagementDataStoreException smdse) {
365: throw new LoginException(
366: "Caught SecurityManagementDataStoreException retrieving user or groups: "
367: + ExceptionUtility
368: .printStackTracesToString(smdse));
369: } catch (UnknownPrincipalException upe) {
370: throw new LoginException(
371: "Error retreving groups for unknown user. "
372: + "Caused by ["
373: + ExceptionUtility
374: .printStackTracesToString(upe)
375: + "]");
376: }
377: }
378:
379: /**
380: * Returns the string prompt associated with the default
381: * <code>NameCallback</code> object.
382: *
383: * @return name prompt
384: * @see javax.security.auth.callback.NameCallback
385: */
386: protected String getUsernamePrompt() {
387: return "username: ";
388: }
389:
390: /**
391: * Returns the string prompt associated with the default
392: * <code>PasswordCallback</code> object.
393: *
394: * @return password prompt
395: * @see javax.security.auth.callback.PasswordCallback
396: */
397: protected String getCredentialPrompt() {
398: return "password: ";
399: }
400: }
|