KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > enterprise > security > auth > realm > file > FileRealm


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24
25 package com.sun.enterprise.security.auth.realm.file;
26
27 import java.lang.*;
28 import java.util.*;
29 import java.util.logging.Logger JavaDoc;
30 import java.util.logging.Level JavaDoc;
31 import com.sun.logging.LogDomains;
32 import java.io.*;
33 import java.security.*;
34 import javax.security.auth.login.*;
35 import com.sun.enterprise.security.auth.realm.User;
36 import com.sun.enterprise.security.auth.realm.Realm;
37 import com.sun.enterprise.security.auth.realm.BadRealmException;
38 import com.sun.enterprise.security.auth.realm.NoSuchUserException;
39 import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
40 import com.sun.enterprise.security.auth.realm.AuthenticationHandler;
41 import com.sun.enterprise.security.auth.realm.InvalidOperationException;
42 import com.sun.enterprise.server.*;
43 import com.sun.enterprise.security.RealmConfig;
44 import com.sun.enterprise.security.util.*;
45 import com.sun.enterprise.security.auth.realm.IASRealm;
46 import sun.misc.BASE64Decoder;
47 import sun.misc.BASE64Encoder;
48
49
50 /**
51  * Realm wrapper for supporting file password authentication.
52  *
53  * <P>In addition to the basic realm functionality, this class provides
54  * administration methods for the file realm.
55  *
56  * <P>Format of the keyfile used by this class is one line per user
57  * containing <code>username;password;groups</code> where:
58  * <ul>
59  * <li>username - Name string.
60  * <li>password - A salted SHA hash (SSHA) of the user password.
61  * <li>groups - A comma separated list of group memberships.
62  * </ul>
63  *
64  * <P>The file realm needs the following properties in its configuration:
65  * <ul>
66  * <li>file - Full path to the keyfile to load
67  * <li>jaas-ctx - JAAS context name used to access LoginModule for
68  * authentication.
69  * </ul>
70  *
71  * @author Harry Singh
72  * @author Jyri Virkki
73  * @author Shing Wai Chan
74  */

75
76 final public class FileRealm extends IASRealm
77 {
78     // Descriptive string of the authentication type of this realm.
79
public static final String JavaDoc AUTH_TYPE = "filepassword";
80
81     // These are property names which should be in auth-realm in server.xml
82
public static final String JavaDoc PARAM_KEYFILE="file";
83
84     // Separators in keyfile (user;pwd-info;group[,group]*)
85
private static final String JavaDoc FIELD_SEP=";";
86     private static final String JavaDoc GROUP_SEP=",";
87     private static final String JavaDoc COMMENT="#";
88
89     // Valid non-alphanumeric/whitespace chars in user/group name
90
public static final String JavaDoc MISC_VALID_CHARS="_-.";
91     
92     // Number of bytes of salt for SSHA
93
private static final int SALT_SIZE=8;
94     
95     // Contains cache of keyfile data
96
private Map userTable; // user=>FileRealmUser
97
private Hashtable groupSizeMap; // maps of groups with value cardinality of group
98

99     private boolean constructed = false;
100     
101
102     /**
103      * Constructor.
104      *
105      * <P>The created FileRealm instance is not registered in the
106      * Realm registry. This constructor can be used by admin tools
107      * to create a FileRealm instance which can be edited by adding or
108      * removing users and then saved to disk, without affecting the
109      * installed realm instance.
110      *
111      * <P>The file provided should always exist. A default (empty) keyfile
112      * is installed with the server so this should always be the case
113      * unless the user has manually deleted this file. If this file
114      * path provided does not point to an existing file this constructor
115      * will first attempt to create it. If this succeeds the constructor
116      * returns normally and an empty keyfile will have been created; otherwise
117      * an exception is thrown.
118      *
119      * @param keyfile Full path to the keyfile to read for user data.
120      * @exception BadRealmException If the configuration parameters
121      * identify a corrupt realm.
122      * @exception NoSuchRealmException If the configuration parameters
123      * specify a realm which doesn't exist.
124      *
125      */

126     public FileRealm(String JavaDoc keyfile)
127          throws BadRealmException, NoSuchRealmException
128     {
129         File fp = new File(keyfile);
130                                 // if not existent, try to create
131
if (!fp.exists()) {
132             FileOutputStream fout = null;
133             try {
134                 fout = new FileOutputStream(fp);
135                 fout.write("\n".getBytes());
136             } catch (Exception JavaDoc e) {
137                 String JavaDoc msg = sm.getString("filerealm.noaccess", e.toString());
138                 throw new BadRealmException(msg);
139             } finally {
140                 if (fout != null) {
141                     try {
142                         fout.close();
143                     } catch(Exception JavaDoc ex) {
144                         // ignore close exception
145
}
146                 }
147             }
148         }
149         
150         constructed = true;
151         Properties p = new Properties();
152         p.setProperty(PARAM_KEYFILE, keyfile);
153         p.setProperty(IASRealm.JAAS_CONTEXT_PARAM, "ignore");
154         this.init(p);
155     }
156
157     
158     /**
159      * Constructor.
160      *
161      * <P>Do not use directly.
162      */

163     public FileRealm()
164     {
165     }
166
167     
168     /**
169      * Initialize a realm with some properties. This can be used
170      * when instantiating realms from their descriptions. This
171      * method is invoked from Realm during initialization.
172      *
173      * @param props Initialization parameters used by this realm.
174      * @exception BadRealmException If the configuration parameters
175      * identify a corrupt realm.
176      * @exception NoSuchRealmException If the configuration parameters
177      * specify a realm which doesn't exist.
178      *
179      */

180     protected void init(Properties props)
181         throws BadRealmException, NoSuchRealmException
182     {
183         String JavaDoc file = props.getProperty(PARAM_KEYFILE);
184         if (file == null) {
185             String JavaDoc msg = sm.getString("filerealm.nofile");
186             throw new BadRealmException(msg);
187         }
188         this.setProperty(PARAM_KEYFILE, file);
189         
190         String JavaDoc jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
191         if (jaasCtx == null) {
192             String JavaDoc msg = sm.getString("filerealm.nomodule");
193             throw new BadRealmException(msg);
194         }
195         this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);
196
197         _logger.fine("FileRealm : "+PARAM_KEYFILE+"="+file);
198         _logger.fine("FileRealm : "+IASRealm.JAAS_CONTEXT_PARAM+"="+
199                      jaasCtx);
200         
201         loadKeyFile();
202     }
203
204
205     /**
206      * Returns a short (preferably less than fifteen characters) description
207      * of the kind of authentication which is supported by this realm.
208      *
209      * @return Description of the kind of authentication that is directly
210      * supported by this realm.
211      */

212     public String JavaDoc getAuthType()
213     {
214         return AUTH_TYPE;
215     }
216     
217
218     /**
219      * Returns names of all the users in this particular realm.
220      *
221      * @return enumeration of user names (strings)
222      * @exception BadRealmException if realm data structures are bad
223      */

224     public Enumeration getUserNames()
225          throws BadRealmException
226     {
227         return (new Vector(userTable.keySet())).elements(); // ugh
228
}
229
230
231     /**
232      * Returns the information recorded about a particular named user.
233      *
234      * @param name Name of the user whose information is desired.
235      * @return The user object.
236      * @exception NoSuchUserException if the user doesn't exist.
237      * @exception BadRealmException if realm data structures are bad.
238      */

239     public User getUser(String JavaDoc name)
240         throws NoSuchUserException
241     {
242         FileRealmUser u = (FileRealmUser)userTable.get(name);
243         if (u == null) {
244             String JavaDoc msg = sm.getString("filerealm.nouser", name);
245             throw new NoSuchUserException(msg);
246         }
247         return u;
248     }
249     
250
251     /**
252      * Returns names of all the groups in this particular realm.
253      *
254      * @return enumeration of group names (strings)
255      * @exception BadRealmException if realm data structures are bad
256      */

257     public Enumeration getGroupNames()
258         throws BadRealmException
259     {
260         return groupSizeMap.keys();
261     }
262
263     
264     /**
265      * Returns the name of all the groups that this user belongs to.
266      * @param username Name of the user in this realm whose group listing
267      * is needed.
268      * @return Enumeration of group names (strings).
269      * @exception InvalidOperationException thrown if the realm does not
270      * support this operation - e.g. Certificate realm does not support
271      * this operation.
272      */

273     public Enumeration getGroupNames(String JavaDoc username)
274         throws NoSuchUserException
275     {
276         FileRealmUser ud = (FileRealmUser)userTable.get(username);
277         if (ud == null) {
278             String JavaDoc msg = sm.getString("filerealm.nouser", username);
279             throw new NoSuchUserException(msg);
280         }
281
282         String JavaDoc[] groups = ud.getGroups();
283         Vector v = new Vector();
284         if (groups != null) {
285             for (int i = 0; i < groups.length; i++) {
286                v.add(groups[i]);
287             }
288         }
289         return v.elements();
290     }
291     
292
293     /**
294      * Refreshes the realm data so that new users/groups are visible.
295      *
296      * <P>A new FileRealm instance is created and initialized from the
297      * keyfile on disk. The new instance is installed in the Realm registry
298      * so future Realm.getInstance() calls will obtain the new data. Any
299      * existing references to this instance (e.g. in active LoginModule
300      * sessions) are unaffected.
301      *
302      * @exception BadRealmException if realm data structures are bad
303      *
304      */

305     public void refresh()
306          throws BadRealmException
307     {
308         _logger.fine("Reloading file realm data.");
309
310         FileRealm newRealm = new FileRealm();
311
312         try {
313             newRealm.init(getProperties());
314             Realm.updateInstance(newRealm, this.getName());
315         } catch (Exception JavaDoc e) {
316             throw new BadRealmException(e.toString());
317         }
318     }
319
320
321     /**
322      * Authenticates a user.
323      *
324      * <P>This method is invoked by the FileLoginModule in order to
325      * authenticate a user in the file realm. The authentication decision
326      * is kept within the realm class implementation in order to keep
327      * the password cache in a single location with no public accessors,
328      * to simplify future improvements.
329      *
330      * @param user Name of user to authenticate.
331      * @param password Password provided by client.
332      * @returns Array of group names the user belongs to, or null if
333      * authentication fails.
334      * @throws LoginException If there are errors during authentication.
335      *
336      */

337     public String JavaDoc[] authenticate(String JavaDoc user, String JavaDoc password)
338     {
339         FileRealmUser ud = (FileRealmUser)userTable.get(user);
340         if (ud == null) {
341             if (_logger.isLoggable(Level.FINE)) {
342                 _logger.fine("No such user: [" + user + "]");
343             }
344             return null;
345         }
346
347         boolean ok = false;
348         try {
349             ok = SSHA.verify(ud.getSalt(), ud.getHash(), password.getBytes());
350
351         } catch (Exception JavaDoc e) {
352             _logger.fine("File authentication failed: "+e.toString());
353             return null;
354         }
355
356         if (!ok) {
357             if (_logger.isLoggable(Level.FINE)) {
358                 _logger.fine("File authentication failed for: ["+user+"]");
359             }
360             return null;
361         }
362         
363         return ud.getGroups();
364     }
365
366     
367
368     //---------------------------------------------------------------------
369
// File realm maintenance methods for admin.
370

371
372     /**
373      * Return false if any char of the string is not alphanumeric or space
374      * or other permitted character.
375      * For a username it will allow an @ symbol. To allow for the case of type
376      * <i>username@foo.com</i>. It will not allow the same symbol for a group name
377      * @param String the name to be validated
378      * @param boolean true if the string is a username, false if it is
379      * a group name
380      *
381      */

382     private static boolean isValid(String JavaDoc s, boolean userName)
383     {
384         for (int i=0; i<s.length(); i++) {
385             char c = s.charAt(i);
386             if (!Character.isLetterOrDigit(c) &&
387                 !Character.isWhitespace(c) &&
388                 MISC_VALID_CHARS.indexOf(c) == -1) {
389                 if (userName && (c == '@')){
390                     continue;
391                 }
392                 return false;
393             }
394         }
395         return true;
396     }
397
398     
399     /**
400      * Validates syntax of a user name.
401      *
402      * <P>This method throws an exception if the provided value is not
403      * valid. The message of the exception provides a reason why it is
404      * not valid. This is used internally by add/modify User to
405      * validate the client-provided values. It is not necessary for
406      * the client to call these methods first. However, these are
407      * provided as public methods for convenience in case some client
408      * (e.g. GUI client) wants to provide independent field validation
409      * prior to calling add/modify user.
410      *
411      * @param name User name to validate.
412      * @throws IASSecurityException Thrown if the value is not valid.
413      *
414      */

415     public static void validateUserName(String JavaDoc name)
416         throws IASSecurityException
417     {
418         if (name == null || name.length() == 0) {
419             String JavaDoc msg = sm.getString("filerealm.noname");
420             throw new IASSecurityException(msg);
421         }
422
423         if (!isValid(name, true)) {
424             String JavaDoc msg = sm.getString("filerealm.badname", name);
425             throw new IASSecurityException(msg);
426         }
427
428         if (!name.equals(name.trim())) {
429             String JavaDoc msg = sm.getString("filerealm.badspaces", name);
430             throw new IASSecurityException(msg);
431         }
432     }
433
434
435     /**
436      * Validates syntax of a password.
437      *
438      * <P>This method throws an exception if the provided value is not
439      * valid. The message of the exception provides a reason why it is
440      * not valid. This is used internally by add/modify User to
441      * validate the client-provided values. It is not necessary for
442      * the client to call these methods first. However, these are
443      * provided as public methods for convenience in case some client
444      * (e.g. GUI client) wants to provide independent field validation
445      * prior to calling add/modify user.
446      *
447      * @param pwd Password to validate.
448      * @throws IASSecurityException Thrown if the value is not valid.
449      *
450      */

451     public static void validatePassword(String JavaDoc pwd)
452         throws IASSecurityException
453     {
454         if (pwd == null) {
455             String JavaDoc msg = sm.getString("filerealm.emptypwd");
456             throw new IASSecurityException(msg);
457         }
458
459         if (!pwd.equals(pwd.trim())) {
460             String JavaDoc msg = sm.getString("filerealm.badspacespwd");
461             throw new IASSecurityException(msg);
462         }
463     }
464
465
466     /**
467      * Validates syntax of a group name.
468      *
469      * <P>This method throws an exception if the provided value is not
470      * valid. The message of the exception provides a reason why it is
471      * not valid. This is used internally by add/modify User to
472      * validate the client-provided values. It is not necessary for
473      * the client to call these methods first. However, these are
474      * provided as public methods for convenience in case some client
475      * (e.g. GUI client) wants to provide independent field validation
476      * prior to calling add/modify user.
477      *
478      * @param group Group name to validate.
479      * @throws IASSecurityException Thrown if the value is not valid.
480      *
481      */

482     public static void validateGroupName(String JavaDoc group)
483         throws IASSecurityException
484     {
485         if (group == null || group.length() == 0) {
486             String JavaDoc msg = sm.getString("filerealm.nogroup");
487             throw new IASSecurityException(msg);
488         }
489
490         if (!isValid(group, false)) {
491             String JavaDoc msg = sm.getString("filerealm.badchars", group);
492             throw new IASSecurityException(msg);
493         }
494         
495         if (!group.equals(group.trim())) {
496             String JavaDoc msg = sm.getString("filerealm.badspaces", group);
497             throw new IASSecurityException(msg);
498         }
499     }
500
501     
502     /**
503      * Validates syntax of a list of group names.
504      *
505      * <P>This is equivalent to calling validateGroupName on every element
506      * of the groupList.
507      *
508      * @param groupList Array of group names to validate.
509      * @throws IASSecurityException Thrown if the value is not valid.
510      *
511      *
512      */

513     public static void validateGroupList(String JavaDoc[] groupList)
514         throws IASSecurityException
515     {
516         if (groupList == null || groupList.length == 0) {
517             return; // empty list is ok
518
}
519
520         for (int i=0; i<groupList.length; i++) {
521             validateGroupName(groupList[i]);
522         }
523         
524     }
525
526     
527     /**
528      * Adds new user to file realm. User cannot exist already.
529      *
530      * @param name User name.
531      * @param password Cleartext password for the user.
532      * @param groupList List of groups to which user belongs.
533      * @throws BadRealmException If there are problems adding user.
534      *
535      */

536     public synchronized void addUser(String JavaDoc name, String JavaDoc password,
537                         String JavaDoc[] groupList)
538         throws BadRealmException, IASSecurityException
539     {
540         validateUserName(name);
541         validatePassword(password);
542         validateGroupList(groupList);
543         
544         if (userTable.containsKey(name)) {
545             String JavaDoc msg = sm.getString("filerealm.dupuser", name);
546             throw new BadRealmException(msg);
547         }
548
549         addGroupNames(groupList);
550         FileRealmUser ud = createNewUser(name, password, groupList);
551         userTable.put(name, ud);
552     }
553
554
555     /**
556      * Remove user from file realm. User must exist.
557      *
558      * @param name User name.
559      * @throws NoSuchUserException If user does not exist.
560      *
561      */

562     public synchronized void removeUser(String JavaDoc name)
563         throws NoSuchUserException
564     {
565         if (!userTable.containsKey(name)) {
566             String JavaDoc msg = sm.getString("filerealm.nouser", name);
567             throw new NoSuchUserException(msg);
568         }
569
570         FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
571         userTable.remove(name);
572         reduceGroups(oldUser.getGroups());
573     }
574
575
576     /**
577      * Update data for an existing user. User must exist. This is equivalent
578      * to calling removeUser() followed by addUser().
579      *
580      * @param name User name.
581      * @param password Cleartext password for the user.
582      * @param groupList List of groups to which user belongs.
583      * @throws BadRealmException If there are problems adding user.
584      * @throws NoSuchUserException If user does not exist.
585      * @deprecated
586      *
587      */

588     public synchronized void updateUser(String JavaDoc name, String JavaDoc password,
589                            String JavaDoc[] groups)
590         throws NoSuchUserException, BadRealmException,
591                                IASSecurityException
592     {
593         updateUser(name, name, password, groups);
594     }
595     
596
597     /**
598      * Update data for an existing user. User must exist.
599      *
600      * @param name Current name of the user to update.
601      * @param newName New name to give this user. It can be the same as
602      * the original name. Otherwise it must be a new user name which
603      * does not already exist as a user.
604      * @param password Cleartext password for the user. If non-null the user
605      * password is changed to this value. If null, the original password
606      * is retained.
607      * @param groupList List of groups to which user belongs.
608      * @throws BadRealmException If there are problems adding user.
609      * @throws NoSuchUserException If user does not exist.
610      *
611      */

612     public synchronized void updateUser(String JavaDoc name, String JavaDoc newName, String JavaDoc password,
613                            String JavaDoc[] groups)
614         throws NoSuchUserException, BadRealmException,
615                                IASSecurityException
616     {
617                                 // user to modify must exist first
618
validateUserName(name);
619         if (!userTable.containsKey(name)) {
620             String JavaDoc msg = sm.getString("filerealm.nouser", name);
621             throw new NoSuchUserException(msg);
622         }
623
624                                 // do general validation
625
validateUserName(newName);
626         validateGroupList(groups);
627         if (password != null) { // null here means re-use previous so is ok
628
validatePassword(password);
629         }
630         
631                                 // can't duplicate unless modifying itself
632
if (!name.equals(newName) && userTable.containsKey(newName)) {
633             String JavaDoc msg = sm.getString("filerealm.dupuser", name);
634             throw new BadRealmException(msg);
635         }
636
637         
638         FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
639         assert (oldUser != null);
640         
641                                 // create user using new name
642
FileRealmUser newUser = new FileRealmUser(newName);
643         
644                                 // set groups as provided by parameter
645
changeGroups(oldUser.getGroups(), groups);
646         newUser.setGroups(groups);
647         
648                                 // use old password if no new pwd given
649
if (password==null) {
650             newUser.setSalt(oldUser.getSalt());
651             newUser.setHash(oldUser.getHash());
652             
653         } else {
654             setPassword(newUser, password);
655         }
656         
657         userTable.remove(name);
658         userTable.put(newName, newUser);
659     }
660     
661         
662     /**
663      * Write keyfile data out to disk. The file generation is sychronized
664      * within this class only, caller is responsible for any other
665      * file locking or revision management as deemed necessary.
666      *
667      * @param filename The name of the output file to create.
668      * @throws IOException If write fails.
669      *
670      */

671     public void writeKeyFile(String JavaDoc filename)
672          throws IOException
673     {
674         synchronized(FileRealm.class) {
675             FileOutputStream out = null;
676             try {
677                 out = new FileOutputStream(filename);
678
679                 Iterator names = userTable.keySet().iterator();
680                 while (names.hasNext()) {
681                 
682                     String JavaDoc name = (String JavaDoc)names.next();
683                     FileRealmUser ud = (FileRealmUser)userTable.get(name);
684
685                     String JavaDoc entry = encodeUser(name, ud);
686                     out.write(entry.getBytes());
687                 }
688             } catch (IOException e) {
689                 throw e;
690
691             } catch (Exception JavaDoc e) {
692                 String JavaDoc msg = sm.getString("filerealm.badwrite", e.toString());
693                 throw new IOException(msg);
694             } finally {
695                 if (out != null) {
696                     out.close();
697                 }
698             }
699         }
700
701         _logger.fine("Done writing "+filename);
702     }
703
704     
705     //---------------------------------------------------------------------
706
// Private methods.
707

708     
709     /**
710      * Add group names to the groups table. It is assumed all entries are
711      * valid group names.
712      *
713      */

714     private void addGroupNames(String JavaDoc[] groupList) {
715         if (groupList != null) {
716             for (int i=0; i < groupList.length; i++) {
717                 Integer JavaDoc groupSize = (Integer JavaDoc)groupSizeMap.get(groupList[i]);
718                 groupSizeMap.put(groupList[i],
719                     (groupSize != null) ?
720                     new Integer JavaDoc(groupSize.intValue() + 1): new Integer JavaDoc(1));
721             }
722         }
723     }
724
725     /**
726      * This method reduces the group size by 1 and remove group name from
727      * internal group list if resulting group size is 0.
728      */

729     private void reduceGroups(String JavaDoc[] groupList) {
730         if (groupList != null) {
731             for (int i=0; i < groupList.length; i++) {
732                 Integer JavaDoc groupSize = (Integer JavaDoc)groupSizeMap.get(groupList[i]);
733                 if (groupSize != null) {
734                     int gpSize = groupSize.intValue() - 1;
735                     if (gpSize > 0) {
736                         groupSizeMap.put(groupList[i], new Integer JavaDoc(gpSize));
737                     } else {
738                         groupSizeMap.remove(groupList[i]);
739                     }
740                 }
741             }
742         }
743     }
744
745     /**
746      * This method update the internal group list.
747      */

748     private void changeGroups(String JavaDoc[] oldGroupList, String JavaDoc[] newGroupList) {
749         addGroupNames(newGroupList);
750         reduceGroups(oldGroupList);
751     }
752     
753     
754     /**
755      * Load keyfile from config and populate internal cache.
756      *
757      */

758     private void loadKeyFile() throws BadRealmException
759     {
760         String JavaDoc file = this.getProperty(PARAM_KEYFILE);
761
762         _logger.fine("Reading file realm: "+file);
763
764         userTable = new Hashtable();
765         groupSizeMap = new Hashtable();
766         BufferedReader input = null;
767         
768         try {
769             input = new BufferedReader(new FileReader(file));
770
771             while (input.ready()) {
772                 
773                 String JavaDoc line = input.readLine();
774                 if (!line.startsWith(COMMENT) &&
775                     line.indexOf(FIELD_SEP) > 0) {
776                     FileRealmUser ud = decodeUser(line, groupSizeMap);
777                     userTable.put(ud.getName(), ud);
778                 }
779             }
780         } catch (Exception JavaDoc e) {
781             _logger.log(Level.WARNING, "filerealm.readerror", e);
782             throw new BadRealmException(e.toString());
783         } finally {
784             if (input != null) {
785                 try {
786                     input.close();
787                 } catch(Exception JavaDoc ex) {
788                 }
789             }
790         }
791     }
792     
793     
794     /**
795      * Encodes one user entry containing info stored in FileRealmUser object.
796      *
797      * @param name User name.
798      * @param ud User object containing info.
799      * @returns String containing a line with encoded user data.
800      * @throws IASSecurityException Thrown on failure.
801      *
802      */

803     private static String JavaDoc encodeUser(String JavaDoc name, FileRealmUser ud)
804     {
805         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
806         String JavaDoc cryptPwd = null;
807
808         sb.append(name);
809         sb.append(FIELD_SEP);
810
811         String JavaDoc ssha = SSHA.encode(ud.getSalt(), ud.getHash());
812
813         sb.append(ssha);
814         sb.append(FIELD_SEP);
815
816         String JavaDoc[] groups = ud.getGroups();
817         if (groups != null) {
818             for (int grp = 0; grp < groups.length; grp++) {
819                 if (grp > 0) {
820                     sb.append(GROUP_SEP);
821                 }
822                 sb.append((String JavaDoc)groups[grp]);
823             }
824         }
825         sb.append("\n");
826         return sb.toString();
827     }
828
829
830     /**
831      * Decodes a line from the keyfile.
832      *
833      * @param encodedLine A line from the keyfile containing user data.
834      * @param newGroupSizeMap Groups found in the encodedLine are added to
835      * this map.
836      * @returns FileRealmUser Representing the loaded user.
837      * @throws IASSecurityException Thrown on failure.
838      *
839      */

840     private static FileRealmUser decodeUser(String JavaDoc encodedLine,
841                                             Map newGroupSizeMap)
842         throws IASSecurityException
843     {
844         StringTokenizer st = new StringTokenizer(encodedLine, FIELD_SEP);
845
846         String JavaDoc user = null;
847         String JavaDoc pwdInfo = null;
848         String JavaDoc groupList = null;
849
850         try { // these must be present
851
user = st.nextToken();
852             pwdInfo = st.nextToken();
853         } catch (Exception JavaDoc e) {
854             String JavaDoc msg = sm.getString("filerealm.syntaxerror", encodedLine);
855             throw new IASSecurityException(msg);
856         }
857         
858         if (st.hasMoreTokens()) { // groups are optional
859
groupList = st.nextToken();
860         }
861
862         byte[] hash = new byte[20];
863         byte[] salt = SSHA.decode(pwdInfo, hash);
864
865         FileRealmUser ud = new FileRealmUser(user);
866         ud.setHash(hash);
867         ud.setSalt(salt);
868         
869         Vector membership = new Vector();
870
871         if (groupList != null) {
872             StringTokenizer gst = new StringTokenizer(groupList,
873                                                       GROUP_SEP);
874             while (gst.hasMoreTokens()) {
875                 String JavaDoc g = gst.nextToken();
876                 membership.add(g);
877                 Integer JavaDoc groupSize = (Integer JavaDoc)newGroupSizeMap.get(g);
878                 newGroupSizeMap.put(g, (groupSize != null) ?
879                     new Integer JavaDoc(groupSize.intValue() + 1) : new Integer JavaDoc(1));
880             }
881         }
882         ud.setGroups(membership);
883         return ud;
884     }
885
886     
887     /**
888      * Produce a user with given data.
889      *
890      * @param name User name.
891      * @param pwd Cleartext password.
892      * @param groups Group membership.
893      * @returns FileRealmUser Representing the created user.
894      * @throws IASSecurityException Thrown on failure.
895      *
896      */

897     private static FileRealmUser createNewUser(String JavaDoc name, String JavaDoc pwd,
898                                                String JavaDoc[] groups)
899         throws IASSecurityException
900     {
901         FileRealmUser ud = new FileRealmUser(name);
902
903         if (groups == null) {
904             groups = new String JavaDoc[0];
905         }
906         ud.setGroups(groups);
907         
908         setPassword(ud, pwd);
909      
910         return ud;
911     }
912
913
914     /**
915      * Sets the password in a user object. Of course the password is not
916      * really stored so a salt is generated, hash computed, and these two
917      * values are stored in the user object provided.
918      *
919      */

920     private static void setPassword(FileRealmUser user, String JavaDoc pwd)
921         throws IASSecurityException
922     {
923         assert (user != null);
924         byte[] pwdBytes = pwd.getBytes();
925         
926         SecureRandom rng=new SecureRandom();
927         byte[] salt=new byte[SALT_SIZE];
928         rng.nextBytes(salt);
929         user.setSalt(salt);
930
931         byte[] hash = SSHA.compute(salt, pwdBytes);
932         user.setHash(hash);
933     }
934
935
936
937     /**
938      * Test method. Not for production use.
939      *
940      */

941     public static void main(String JavaDoc[] args)
942     {
943         if (args.length==0) {
944             help();
945         }
946
947         try {
948             if ("-c".equals(args[0])) {
949                 String JavaDoc[] groups=new String JavaDoc[0];
950                 if (args.length>3) {
951                     groups=new String JavaDoc[args.length-3];
952                     for (int i=3; i<args.length; i++) {
953                         groups[i-3]=args[i];
954                     }
955                 }
956                 FileRealmUser ud = createNewUser(args[1], args[2], groups);
957                 String JavaDoc out=encodeUser(args[1], ud);
958                 System.out.println(out);
959                 
960                 FileRealmUser u=decodeUser(out, new Hashtable());
961                 System.out.println("verifies: "+
962                                    SSHA.verify(u.getSalt(), u.getHash(),
963                                                args[2].getBytes()));
964
965             } else if ("-v".equals(args[0])) {
966                 FileRealmUser u=decodeUser(args[2], new Hashtable());
967                 System.out.println("user: "+u.getName());
968                 System.out.println("verifies: "+
969                                    SSHA.verify(u.getSalt(), u.getHash(),
970                                                args[1].getBytes()));
971             }
972         } catch (Exception JavaDoc e) {
973             e.printStackTrace(System.out);
974         }
975     }
976     /**
977      * Show help for the test command line tool.
978      *
979      */

980     private static void help()
981     {
982         System.out.println("FileRealm -c <name <pwd [group]*");
983         System.out.println("FileRealm -v <pwd `output of -c`");
984         System.exit(1);
985     }
986
987
988
989 }
990
Popular Tags