KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > jmx > remote > security > FileLoginModule


1 /*
2  * @(#)FileLoginModule.java 1.4 06/09/29
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package com.sun.jmx.remote.security;
9
10 import java.io.BufferedInputStream JavaDoc;
11 import java.io.File JavaDoc;
12 import java.io.FileInputStream JavaDoc;
13 import java.io.FilePermission JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.security.AccessControlException JavaDoc;
16 import java.security.AccessController JavaDoc;
17 import java.util.Arrays JavaDoc;
18 import java.util.Hashtable JavaDoc;
19 import java.util.Map JavaDoc;
20 import java.util.Properties JavaDoc;
21
22 import javax.security.auth.*;
23 import javax.security.auth.callback.*;
24 import javax.security.auth.login.*;
25 import javax.security.auth.spi.*;
26 import javax.management.remote.JMXPrincipal JavaDoc;
27
28 import com.sun.jmx.remote.util.ClassLogger;
29 import com.sun.jmx.remote.util.EnvHelp;
30 import sun.management.jmxremote.ConnectorBootstrap;
31
32 import sun.security.action.GetPropertyAction;
33
34 /**
35  * This {@link LoginModule} performs file-based authentication.
36  *
37  * <p> A supplied username and password is verified against the
38  * corresponding user credentials stored in a designated password file.
39  * If successful then a new {@link JMXPrincipal} is created with the
40  * user's name and it is associated with the current {@link Subject}.
41  * Such principals may be identified and granted management privileges in
42  * the access control file for JMX remote management or in a Java security
43  * policy.
44  *
45  * <p> The password file comprises a list of key-value pairs as specified in
46  * {@link Properties}. The key represents a user's name and the value is its
47  * associated cleartext password. By default, the following password file is
48  * used:
49  * <pre>
50  * ${java.home}/lib/management/jmxremote.password
51  * </pre>
52  * A different password file can be specified via the <code>passwordFile</code>
53  * configuration option.
54  *
55  * <p> This module recognizes the following <code>Configuration</code> options:
56  * <dl>
57  * <dt> <code>passwordFile</code> </dt>
58  * <dd> the path to an alternative password file. It is used instead of
59  * the default password file.</dd>
60  *
61  * <dt> <code>useFirstPass</code> </dt>
62  * <dd> if <code>true</code>, this module retrieves the username and password
63  * from the module's shared state, using "javax.security.auth.login.name"
64  * and "javax.security.auth.login.password" as the respective keys. The
65  * retrieved values are used for authentication. If authentication fails,
66  * no attempt for a retry is made, and the failure is reported back to
67  * the calling application.</dd>
68  *
69  * <dt> <code>tryFirstPass</code> </dt>
70  * <dd> if <code>true</code>, this module retrieves the username and password
71  * from the module's shared state, using "javax.security.auth.login.name"
72  * and "javax.security.auth.login.password" as the respective keys. The
73  * retrieved values are used for authentication. If authentication fails,
74  * the module uses the CallbackHandler to retrieve a new username and
75  * password, and another attempt to authenticate is made. If the
76  * authentication fails, the failure is reported back to the calling
77  * application.</dd>
78  *
79  * <dt> <code>storePass</code> </dt>
80  * <dd> if <code>true</code>, this module stores the username and password
81  * obtained from the CallbackHandler in the module's shared state, using
82  * "javax.security.auth.login.name" and
83  * "javax.security.auth.login.password" as the respective keys. This is
84  * not performed if existing values already exist for the username and
85  * password in the shared state, or if authentication fails.</dd>
86  *
87  * <dt> <code>clearPass</code> </dt>
88  * <dd> if <code>true</code>, this module clears the username and password
89  * stored in the module's shared state after both phases of authentication
90  * (login and commit) have completed.</dd>
91  * </dl>
92  */

93 public class FileLoginModule implements LoginModule {
94
95     // Location of the default password file
96
private static final String JavaDoc DEFAULT_PASSWORD_FILE_NAME =
97     ((String JavaDoc) AccessController.doPrivileged(
98                    new GetPropertyAction("java.home"))) +
99     File.separatorChar + "lib" +
100     File.separatorChar + "management" + File.separatorChar +
101     ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
102
103     // Key to retrieve the stored username
104
private static final String JavaDoc USERNAME_KEY =
105     "javax.security.auth.login.name";
106
107     // Key to retrieve the stored password
108
private static final String JavaDoc PASSWORD_KEY =
109     "javax.security.auth.login.password";
110
111     // Log messages
112
private static final ClassLogger logger =
113         new ClassLogger("javax.management.remote.misc", "FileLoginModule");
114
115     // Configurable options
116
private boolean useFirstPass = false;
117     private boolean tryFirstPass = false;
118     private boolean storePass = false;
119     private boolean clearPass = false;
120
121     // Authentication status
122
private boolean succeeded = false;
123     private boolean commitSucceeded = false;
124
125     // Supplied username and password
126
private String JavaDoc username;
127     private char[] password;
128     private JMXPrincipal JavaDoc user;
129
130     // Initial state
131
private Subject subject;
132     private CallbackHandler callbackHandler;
133     private Map JavaDoc sharedState;
134     private Map JavaDoc options;
135     private String JavaDoc passwordFile;
136     private String JavaDoc passwordFileDisplayName;
137     private boolean userSuppliedPasswordFile;
138     private boolean hasJavaHomePermission;
139     private Properties JavaDoc userCredentials;
140
141     /**
142      * Initialize this <code>LoginModule</code>.
143      *
144      * @param subject the <code>Subject</code> to be authenticated.
145      * @param callbackHandler a <code>CallbackHandler</code> to acquire the
146      * user's name and password.
147      * @param sharedState shared <code>LoginModule</code> state.
148      * @param options options specified in the login
149      * <code>Configuration</code> for this particular
150      * <code>LoginModule</code>.
151      */

152     public void initialize(Subject subject, CallbackHandler callbackHandler,
153                Map JavaDoc<String JavaDoc,?> sharedState,
154                Map JavaDoc<String JavaDoc,?> options)
155     {
156
157     this.subject = subject;
158     this.callbackHandler = callbackHandler;
159     this.sharedState = sharedState;
160     this.options = options;
161
162     // initialize any configured options
163
tryFirstPass =
164         "true".equalsIgnoreCase((String JavaDoc)options.get("tryFirstPass"));
165     useFirstPass =
166         "true".equalsIgnoreCase((String JavaDoc)options.get("useFirstPass"));
167     storePass =
168         "true".equalsIgnoreCase((String JavaDoc)options.get("storePass"));
169     clearPass =
170         "true".equalsIgnoreCase((String JavaDoc)options.get("clearPass"));
171
172     passwordFile = (String JavaDoc)options.get("passwordFile");
173     passwordFileDisplayName = passwordFile;
174         userSuppliedPasswordFile = true;
175
176     // set the location of the password file
177
if (passwordFile == null) {
178         passwordFile = DEFAULT_PASSWORD_FILE_NAME;
179             userSuppliedPasswordFile = false;
180             try {
181                 System.getProperty("java.home");
182                 hasJavaHomePermission = true;
183                 passwordFileDisplayName = passwordFile;
184             } catch (SecurityException JavaDoc e) {
185                 hasJavaHomePermission = false;
186                 passwordFileDisplayName =
187                         ConnectorBootstrap.DefaultValues.PASSWORD_FILE_NAME;
188             }
189     }
190     }
191
192     /**
193      * Begin user authentication (Authentication Phase 1).
194      *
195      * <p> Acquire the user's name and password and verify them against
196      * the corresponding credentials from the password file.
197      *
198      * @return true always, since this <code>LoginModule</code>
199      * should not be ignored.
200      * @exception FailedLoginException if the authentication fails.
201      * @exception LoginException if this <code>LoginModule</code>
202      * is unable to perform the authentication.
203      */

204     public boolean login() throws LoginException {
205
206     try {
207         loadPasswordFile();
208     } catch (IOException JavaDoc ioe) {
209         LoginException le = new LoginException(
210                     "Error: unable to load the password file: " +
211                     passwordFileDisplayName);
212         throw (LoginException) EnvHelp.initCause(le, ioe);
213     }
214
215     if (userCredentials == null) {
216         throw new LoginException
217         ("Error: unable to locate the users' credentials.");
218     }
219
220     if (logger.debugOn()) {
221         logger.debug("login",
222                     "Using password file: " + passwordFileDisplayName);
223     }
224
225     // attempt the authentication
226
if (tryFirstPass) {
227
228         try {
229         // attempt the authentication by getting the
230
// username and password from shared state
231
attemptAuthentication(true);
232
233         // authentication succeeded
234
succeeded = true;
235         if (logger.debugOn()) {
236             logger.debug("login",
237             "Authentication using cached password has succeeded");
238         }
239         return true;
240
241         } catch (LoginException le) {
242         // authentication failed -- try again below by prompting
243
cleanState();
244         logger.debug("login",
245             "Authentication using cached password has failed");
246         }
247
248     } else if (useFirstPass) {
249
250         try {
251         // attempt the authentication by getting the
252
// username and password from shared state
253
attemptAuthentication(true);
254
255         // authentication succeeded
256
succeeded = true;
257         if (logger.debugOn()) {
258             logger.debug("login",
259             "Authentication using cached password has succeeded");
260         }
261         return true;
262
263         } catch (LoginException le) {
264         // authentication failed
265
cleanState();
266         logger.debug("login",
267             "Authentication using cached password has failed");
268
269         throw le;
270         }
271     }
272
273     if (logger.debugOn()) {
274         logger.debug("login", "Acquiring password");
275     }
276
277     // attempt the authentication using the supplied username and password
278
try {
279         attemptAuthentication(false);
280
281         // authentication succeeded
282
succeeded = true;
283         if (logger.debugOn()) {
284         logger.debug("login", "Authentication has succeeded");
285         }
286         return true;
287
288     } catch (LoginException le) {
289         cleanState();
290         logger.debug("login", "Authentication has failed");
291
292         throw le;
293     }
294     }
295
296     /**
297      * Complete user authentication (Authentication Phase 2).
298      *
299      * <p> This method is called if the LoginContext's
300      * overall authentication has succeeded
301      * (all the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
302      * LoginModules have succeeded).
303      *
304      * <p> If this LoginModule's own authentication attempt
305      * succeeded (checked by retrieving the private state saved by the
306      * <code>login</code> method), then this method associates a
307      * <code>JMXPrincipal</code> with the <code>Subject</code> located in the
308      * <code>LoginModule</code>. If this LoginModule's own
309      * authentication attempted failed, then this method removes
310      * any state that was originally saved.
311      *
312      * @exception LoginException if the commit fails
313      * @return true if this LoginModule's own login and commit
314      * attempts succeeded, or false otherwise.
315      */

316     public boolean commit() throws LoginException {
317
318     if (succeeded == false) {
319         return false;
320     } else {
321         if (subject.isReadOnly()) {
322         cleanState();
323         throw new LoginException("Subject is read-only");
324         }
325         // add Principals to the Subject
326
if (!subject.getPrincipals().contains(user)) {
327         subject.getPrincipals().add(user);
328         }
329
330         if (logger.debugOn()) {
331         logger.debug("commit",
332             "Authentication has completed successfully");
333         }
334     }
335     // in any case, clean out state
336
cleanState();
337     commitSucceeded = true;
338     return true;
339     }
340
341     /**
342      * Abort user authentication (Authentication Phase 2).
343      *
344      * <p> This method is called if the LoginContext's overall authentication
345      * failed (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
346      * LoginModules did not succeed).
347      *
348      * <p> If this LoginModule's own authentication attempt
349      * succeeded (checked by retrieving the private state saved by the
350      * <code>login</code> and <code>commit</code> methods),
351      * then this method cleans up any state that was originally saved.
352      *
353      * @exception LoginException if the abort fails.
354      * @return false if this LoginModule's own login and/or commit attempts
355      * failed, and true otherwise.
356      */

357     public boolean abort() throws LoginException {
358
359     if (logger.debugOn()) {
360         logger.debug("abort",
361         "Authentication has not completed successfully");
362     }
363
364     if (succeeded == false) {
365         return false;
366     } else if (succeeded == true && commitSucceeded == false) {
367
368         // Clean out state
369
succeeded = false;
370         cleanState();
371         user = null;
372     } else {
373         // overall authentication succeeded and commit succeeded,
374
// but someone else's commit failed
375
logout();
376     }
377     return true;
378     }
379
380     /**
381      * Logout a user.
382      *
383      * <p> This method removes the Principals
384      * that were added by the <code>commit</code> method.
385      *
386      * @exception LoginException if the logout fails.
387      * @return true in all cases since this <code>LoginModule</code>
388      * should not be ignored.
389      */

390     public boolean logout() throws LoginException {
391     if (subject.isReadOnly()) {
392         cleanState();
393         throw new LoginException ("Subject is read-only");
394     }
395     subject.getPrincipals().remove(user);
396     
397     // clean out state
398
cleanState();
399     succeeded = false;
400     commitSucceeded = false;
401     user = null;
402
403     if (logger.debugOn()) {
404         logger.debug("logout", "Subject is being logged out");
405     }
406
407     return true;
408     }
409
410     /**
411      * Attempt authentication
412      *
413      * @param usePasswdFromSharedState a flag to tell this method whether
414      * to retrieve the password from the sharedState.
415      */

416     private void attemptAuthentication(boolean usePasswdFromSharedState)
417     throws LoginException {
418
419     // get the username and password
420
getUsernamePassword(usePasswdFromSharedState);
421     
422     String JavaDoc localPassword = null;
423
424     // userCredentials is initialized in login()
425
if (((localPassword = userCredentials.getProperty(username)) == null) ||
426         (! localPassword.equals(new String JavaDoc(password)))) {
427
428         // username not found or passwords do not match
429
if (logger.debugOn()) {
430         logger.debug("login", "Invalid username or password");
431         }
432         throw new FailedLoginException("Invalid username or password");
433     }
434
435     // Save the username and password in the shared state
436
// only if authentication succeeded
437
if (storePass &&
438         !sharedState.containsKey(USERNAME_KEY) &&
439         !sharedState.containsKey(PASSWORD_KEY)) {
440         sharedState.put(USERNAME_KEY, username);
441         sharedState.put(PASSWORD_KEY, password);
442     }
443
444     // Create a new user principal
445
user = new JMXPrincipal JavaDoc(username);
446
447     if (logger.debugOn()) {
448         logger.debug("login",
449         "User '" + username + "' successfully validated");
450     }
451     }
452
453     /*
454      * Read the password file.
455      */

456     private void loadPasswordFile() throws IOException JavaDoc {
457         FileInputStream JavaDoc fis;
458         try {
459             fis = new FileInputStream JavaDoc(passwordFile);
460         } catch (SecurityException JavaDoc e) {
461             if (userSuppliedPasswordFile || hasJavaHomePermission) {
462                 throw e;
463             } else {
464                 FilePermission JavaDoc fp =
465                         new FilePermission JavaDoc(passwordFileDisplayName, "read");
466                 AccessControlException JavaDoc ace = new AccessControlException JavaDoc(
467                         "access denied " + fp.toString());
468                 ace.setStackTrace(e.getStackTrace());
469                 throw ace;
470             }
471         }
472         BufferedInputStream JavaDoc bis = new BufferedInputStream JavaDoc(fis);
473         userCredentials = new Properties JavaDoc();
474         userCredentials.load(bis);
475         bis.close();
476     }
477
478     /**
479      * Get the username and password.
480      * This method does not return any value.
481      * Instead, it sets global name and password variables.
482      *
483      * <p> Also note that this method will set the username and password
484      * values in the shared state in case subsequent LoginModules
485      * want to use them via use/tryFirstPass.
486      *
487      * @param usePasswdFromSharedState boolean that tells this method whether
488      * to retrieve the password from the sharedState.
489      */

490     private void getUsernamePassword(boolean usePasswdFromSharedState)
491     throws LoginException {
492
493     if (usePasswdFromSharedState) {
494         // use the password saved by the first module in the stack
495
username = (String JavaDoc)sharedState.get(USERNAME_KEY);
496         password = (char[])sharedState.get(PASSWORD_KEY);
497         return;
498     }
499
500     // acquire username and password
501
if (callbackHandler == null)
502         throw new LoginException("Error: no CallbackHandler available " +
503         "to garner authentication information from the user");
504
505     Callback[] callbacks = new Callback[2];
506     callbacks[0] = new NameCallback("username");
507     callbacks[1] = new PasswordCallback("password", false);
508
509     try {
510         callbackHandler.handle(callbacks);
511         username = ((NameCallback)callbacks[0]).getName();
512         char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
513         password = new char[tmpPassword.length];
514         System.arraycopy(tmpPassword, 0,
515                 password, 0, tmpPassword.length);
516         ((PasswordCallback)callbacks[1]).clearPassword();
517
518     } catch (IOException JavaDoc ioe) {
519         LoginException le = new LoginException(ioe.toString());
520         throw (LoginException) EnvHelp.initCause(le, ioe);
521     } catch (UnsupportedCallbackException uce) {
522             LoginException le = new LoginException(
523                                     "Error: " + uce.getCallback().toString() +
524                                     " not available to garner authentication " +
525                                     "information from the user");
526             throw (LoginException) EnvHelp.initCause(le, uce);
527     }
528     }
529
530     /**
531      * Clean out state because of a failed authentication attempt
532      */

533     private void cleanState() {
534     username = null;
535     if (password != null) {
536         Arrays.fill(password, ' ');
537         password = null;
538     }
539
540     if (clearPass) {
541         sharedState.remove(USERNAME_KEY);
542         sharedState.remove(PASSWORD_KEY);
543     }
544     }
545 }
546
Popular Tags