FileDocCategorySizeDatePackage
FileRealm.javaAPI DocGlassfish v2 API34114Fri May 04 22:35:30 BST 2007com.sun.enterprise.security.auth.realm.file

FileRealm

public final class FileRealm extends com.sun.enterprise.security.auth.realm.IASRealm
Realm wrapper for supporting file password authentication.

In addition to the basic realm functionality, this class provides administration methods for the file realm.

Format of the keyfile used by this class is one line per user containing username;password;groups where:

  • username - Name string.
  • password - A salted SHA hash (SSHA) of the user password.
  • groups - A comma separated list of group memberships.

The file realm needs the following properties in its configuration:

  • file - Full path to the keyfile to load
  • jaas-ctx - JAAS context name used to access LoginModule for authentication.
author
Harry Singh
author
Jyri Virkki
author
Shing Wai Chan

Fields Summary
public static final String
AUTH_TYPE
public static final String
PARAM_KEYFILE
private static final String
FIELD_SEP
private static final String
GROUP_SEP
private static final String
COMMENT
public static final String
MISC_VALID_CHARS
private static final int
SALT_SIZE
private Map
userTable
private Hashtable
groupSizeMap
private boolean
constructed
Constructors Summary
public FileRealm(String keyfile)
Constructor.

The created FileRealm instance is not registered in the Realm registry. This constructor can be used by admin tools to create a FileRealm instance which can be edited by adding or removing users and then saved to disk, without affecting the installed realm instance.

The file provided should always exist. A default (empty) keyfile is installed with the server so this should always be the case unless the user has manually deleted this file. If this file path provided does not point to an existing file this constructor will first attempt to create it. If this succeeds the constructor returns normally and an empty keyfile will have been created; otherwise an exception is thrown.

param
keyfile Full path to the keyfile to read for user data.
exception
BadRealmException If the configuration parameters identify a corrupt realm.
exception
NoSuchRealmException If the configuration parameters specify a realm which doesn't exist.

    

                                                                                                                                                                       
      
           
    
        File fp = new File(keyfile);
                                // if not existent, try to create
        if (!fp.exists()) {
            FileOutputStream fout = null;
            try {
                fout =  new FileOutputStream(fp);
                fout.write("\n".getBytes());
            } catch (Exception e) {
                String msg = sm.getString("filerealm.noaccess", e.toString());
                throw new BadRealmException(msg);
            } finally {
                if (fout != null) {
                    try {
                        fout.close();
                    } catch(Exception ex) {
                        // ignore close exception
                    }
                }
            }
        }
        
        constructed = true;
        Properties p = new Properties();
        p.setProperty(PARAM_KEYFILE, keyfile);
        p.setProperty(IASRealm.JAAS_CONTEXT_PARAM, "ignore");
        this.init(p);
    
public FileRealm()
Constructor.

Do not use directly.

    
Methods Summary
private voidaddGroupNames(java.lang.String[] groupList)
Add group names to the groups table. It is assumed all entries are valid group names.

        if (groupList != null) {
            for (int i=0; i < groupList.length; i++) {
                Integer groupSize = (Integer)groupSizeMap.get(groupList[i]);
                groupSizeMap.put(groupList[i],
                    (groupSize != null) ?
                    new Integer(groupSize.intValue() + 1): new Integer(1));
            }
        }
    
public synchronized voidaddUser(java.lang.String name, java.lang.String password, java.lang.String[] groupList)
Adds new user to file realm. User cannot exist already.

param
name User name.
param
password Cleartext password for the user.
param
groupList List of groups to which user belongs.
throws
BadRealmException If there are problems adding user.

        validateUserName(name);
        validatePassword(password);
        validateGroupList(groupList);
        
        if (userTable.containsKey(name)) {
            String msg = sm.getString("filerealm.dupuser", name);
            throw new BadRealmException(msg);
        }

        addGroupNames(groupList);        
        FileRealmUser ud = createNewUser(name, password, groupList);
        userTable.put(name, ud);
    
public java.lang.String[]authenticate(java.lang.String user, java.lang.String password)
Authenticates a user.

This method is invoked by the FileLoginModule in order to authenticate a user in the file realm. The authentication decision is kept within the realm class implementation in order to keep the password cache in a single location with no public accessors, to simplify future improvements.

param
user Name of user to authenticate.
param
password Password provided by client.
returns
Array of group names the user belongs to, or null if authentication fails.
throws
LoginException If there are errors during authentication.

        FileRealmUser ud = (FileRealmUser)userTable.get(user);
        if (ud == null) {
            if (_logger.isLoggable(Level.FINE)) {
                _logger.fine("No such user: [" + user + "]");
            }
            return null;
        }

        boolean ok = false;
        try {
            ok = SSHA.verify(ud.getSalt(), ud.getHash(), password.getBytes());

        } catch (Exception e) {
            _logger.fine("File authentication failed: "+e.toString());
            return null;
        }

        if (!ok) {
            if (_logger.isLoggable(Level.FINE)) {
                _logger.fine("File authentication failed for: ["+user+"]");
            }
            return null;
        }
        
        String[] groups = ud.getGroups();
        groups = addAssignGroups(groups);
        return groups;
    
private voidchangeGroups(java.lang.String[] oldGroupList, java.lang.String[] newGroupList)
This method update the internal group list.

        addGroupNames(newGroupList);
        reduceGroups(oldGroupList);
    
private static FileRealmUsercreateNewUser(java.lang.String name, java.lang.String pwd, java.lang.String[] groups)
Produce a user with given data.

param
name User name.
param
pwd Cleartext password.
param
groups Group membership.
returns
FileRealmUser Representing the created user.
throws
IASSecurityException Thrown on failure.

        FileRealmUser ud = new FileRealmUser(name);

        if (groups == null) {
            groups = new String[0];
        }
        ud.setGroups(groups);
        
        setPassword(ud, pwd);
     
        return ud;
    
private static FileRealmUserdecodeUser(java.lang.String encodedLine, java.util.Map newGroupSizeMap)
Decodes a line from the keyfile.

param
encodedLine A line from the keyfile containing user data.
param
newGroupSizeMap Groups found in the encodedLine are added to this map.
returns
FileRealmUser Representing the loaded user.
throws
IASSecurityException Thrown on failure.

        StringTokenizer st = new StringTokenizer(encodedLine, FIELD_SEP);

        String user = null;
        String pwdInfo = null;
        String groupList = null;

        try {                   // these must be present
            user = st.nextToken();
            pwdInfo = st.nextToken();
        } catch (Exception e) {
            String msg = sm.getString("filerealm.syntaxerror", encodedLine);
            throw new IASSecurityException(msg);
        }
        
        if (st.hasMoreTokens()) { // groups are optional
            groupList = st.nextToken();
        }

        byte[] hash = new byte[20];
        byte[] salt = SSHA.decode(pwdInfo, hash);

        FileRealmUser ud = new FileRealmUser(user);
        ud.setHash(hash);
        ud.setSalt(salt);
        
        Vector membership = new Vector();

        if (groupList != null) {
            StringTokenizer gst = new StringTokenizer(groupList,
                                                      GROUP_SEP);
            while (gst.hasMoreTokens()) {
                String g = gst.nextToken();
                membership.add(g);
                Integer groupSize = (Integer)newGroupSizeMap.get(g);
                newGroupSizeMap.put(g, (groupSize != null) ?
                    new Integer(groupSize.intValue() + 1) : new Integer(1));
            }
        }
        ud.setGroups(membership);
        return ud;
    
private static java.lang.StringencodeUser(java.lang.String name, FileRealmUser ud)
Encodes one user entry containing info stored in FileRealmUser object.

param
name User name.
param
ud User object containing info.
returns
String containing a line with encoded user data.
throws
IASSecurityException Thrown on failure.

        StringBuffer sb = new StringBuffer();
        String cryptPwd = null;

        sb.append(name);
        sb.append(FIELD_SEP);

        String ssha = SSHA.encode(ud.getSalt(), ud.getHash());

        sb.append(ssha);
        sb.append(FIELD_SEP);

        String[] groups = ud.getGroups();
        if (groups != null) {
            for (int grp = 0; grp < groups.length; grp++) {
                if (grp > 0) {
                    sb.append(GROUP_SEP);
                }
                sb.append((String)groups[grp]);
            }
        }
        sb.append("\n");
        return sb.toString();
    
public java.lang.StringgetAuthType()
Returns a short (preferably less than fifteen characters) description of the kind of authentication which is supported by this realm.

return
Description of the kind of authentication that is directly supported by this realm.

        return AUTH_TYPE;
    
public java.util.EnumerationgetGroupNames()
Returns names of all the groups in this particular realm. Note that this will not return assign-groups.

return
enumeration of group names (strings)
exception
BadRealmException if realm data structures are bad

        return groupSizeMap.keys();
    
public java.util.EnumerationgetGroupNames(java.lang.String username)
Returns the name of all the groups that this user belongs to.

param
username Name of the user in this realm whose group listing is needed.
return
Enumeration of group names (strings).
exception
InvalidOperationException thrown if the realm does not support this operation - e.g. Certificate realm does not support this operation.

        FileRealmUser ud = (FileRealmUser)userTable.get(username);
        if (ud == null) {
            String msg = sm.getString("filerealm.nouser", username);
            throw new NoSuchUserException(msg);
        }

        String[] groups = ud.getGroups();
        groups = addAssignGroups(groups);
        Vector v = new Vector();
        if (groups != null) {
            for (int i = 0; i < groups.length; i++) {
               v.add(groups[i]);
            }
        }
        return v.elements();
    
public com.sun.enterprise.security.auth.realm.UsergetUser(java.lang.String name)
Returns the information recorded about a particular named user.

param
name Name of the user whose information is desired.
return
The user object.
exception
NoSuchUserException if the user doesn't exist.
exception
BadRealmException if realm data structures are bad.

        FileRealmUser u = (FileRealmUser)userTable.get(name);
        if (u == null) {
            String msg = sm.getString("filerealm.nouser", name);
            throw new NoSuchUserException(msg);
        }
        return u;
    
public java.util.EnumerationgetUserNames()
Returns names of all the users in this particular realm.

return
enumeration of user names (strings)
exception
BadRealmException if realm data structures are bad

        return (new Vector(userTable.keySet())).elements(); // ugh
    
private static voidhelp()
Show help for the test command line tool.

        System.out.println("FileRealm -c <name  <pwd  [group]*");
        System.out.println("FileRealm -v <pwd  `output of -c`");
        System.exit(1);
    
protected voidinit(java.util.Properties props)
Initialize a realm with some properties. This can be used when instantiating realms from their descriptions. This method is invoked from Realm during initialization.

param
props Initialization parameters used by this realm.
exception
BadRealmException If the configuration parameters identify a corrupt realm.
exception
NoSuchRealmException If the configuration parameters specify a realm which doesn't exist.

        super.init(props);
        String file = props.getProperty(PARAM_KEYFILE);
        if (file == null) {
            String msg = sm.getString("filerealm.nofile");
            throw new BadRealmException(msg);
        }
        this.setProperty(PARAM_KEYFILE, file);
        
        String jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM);
        if (jaasCtx == null) {
            String msg = sm.getString("filerealm.nomodule");
            throw new BadRealmException(msg);
        }
        this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx);

        if (_logger.isLoggable(Level.FINE)) {
            _logger.fine("FileRealm : "+PARAM_KEYFILE+"="+file);
            _logger.fine("FileRealm : "+IASRealm.JAAS_CONTEXT_PARAM+"="+
                     jaasCtx);
        }
        
        loadKeyFile();
    
private static booleanisValid(java.lang.String s, boolean userName)
Return false if any char of the string is not alphanumeric or space or other permitted character. For a username it will allow an @ symbol. To allow for the case of type username@foo.com. It will not allow the same symbol for a group name

param
String the name to be validated
param
boolean true if the string is a username, false if it is a group name

        for (int i=0; i<s.length(); i++) {
            char c = s.charAt(i);
            if (!Character.isLetterOrDigit(c) &&
                !Character.isWhitespace(c) &&
                MISC_VALID_CHARS.indexOf(c) == -1) {
                if (userName && (c == '@")){
                    continue;
                }
                return false;
            }
        }
        return true;
    
private voidloadKeyFile()
Load keyfile from config and populate internal cache.

        String file = this.getProperty(PARAM_KEYFILE);

        _logger.fine("Reading file realm: "+file);

        userTable = new Hashtable();
        groupSizeMap = new Hashtable();
        BufferedReader input = null;
        
        try {
            input = new BufferedReader(new FileReader(file));

            while (input.ready()) {
                
                String line = input.readLine();
                if (!line.startsWith(COMMENT) &&
                    line.indexOf(FIELD_SEP) > 0) {
                    FileRealmUser ud = decodeUser(line, groupSizeMap);
                    userTable.put(ud.getName(), ud);
                }
            }
        } catch (Exception e) {
            _logger.log(Level.WARNING, "filerealm.readerror", e);
            throw new BadRealmException(e.toString());
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch(Exception ex) {
                }
            }
        }
    
public static voidmain(java.lang.String[] args)
Test method. Not for production use.

        if (args.length==0) {
            help();
        }

        try {
            if ("-c".equals(args[0])) {
                String[] groups=new String[0];
                if (args.length>3) {
                    groups=new String[args.length-3];
                    for (int i=3; i<args.length; i++) {
                        groups[i-3]=args[i];
                    }
                }
                FileRealmUser ud = createNewUser(args[1], args[2], groups);
                String out=encodeUser(args[1], ud);
                System.out.println(out);
                
                FileRealmUser u=decodeUser(out, new Hashtable());
                System.out.println("verifies: "+
                                   SSHA.verify(u.getSalt(), u.getHash(),
                                               args[2].getBytes()));

            } else if ("-v".equals(args[0])) {
                FileRealmUser u=decodeUser(args[2], new Hashtable());
                System.out.println("user: "+u.getName());
                System.out.println("verifies: "+
                                   SSHA.verify(u.getSalt(), u.getHash(),
                                               args[1].getBytes()));
            }
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    
private voidreduceGroups(java.lang.String[] groupList)
This method reduces the group size by 1 and remove group name from internal group list if resulting group size is 0.

        if (groupList != null) {
            for (int i=0; i < groupList.length; i++) {
                Integer groupSize = (Integer)groupSizeMap.get(groupList[i]);
                if (groupSize != null) {
                    int gpSize = groupSize.intValue() - 1;
                    if (gpSize > 0) {
                        groupSizeMap.put(groupList[i], new Integer(gpSize));
                    } else {
                        groupSizeMap.remove(groupList[i]);
                    }
                }
            }
        }
    
public voidrefresh()
Refreshes the realm data so that new users/groups are visible.

A new FileRealm instance is created and initialized from the keyfile on disk. The new instance is installed in the Realm registry so future Realm.getInstance() calls will obtain the new data. Any existing references to this instance (e.g. in active LoginModule sessions) are unaffected.

exception
BadRealmException if realm data structures are bad

        if (_logger.isLoggable(Level.FINE)) {
            _logger.fine("Reloading file realm data.");
        }

        FileRealm newRealm = new FileRealm();

        try {
            newRealm.init(getProperties());
            Realm.updateInstance(newRealm, this.getName());
        } catch (Exception e) {
            throw new BadRealmException(e.toString());
        }
    
public synchronized voidremoveUser(java.lang.String name)
Remove user from file realm. User must exist.

param
name User name.
throws
NoSuchUserException If user does not exist.

        if (!userTable.containsKey(name)) {
            String msg = sm.getString("filerealm.nouser", name);
            throw new NoSuchUserException(msg);
        }

        FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
        userTable.remove(name);
        reduceGroups(oldUser.getGroups());
    
private static voidsetPassword(FileRealmUser user, java.lang.String pwd)
Sets the password in a user object. Of course the password is not really stored so a salt is generated, hash computed, and these two values are stored in the user object provided.

        assert (user != null);
        byte[] pwdBytes = pwd.getBytes();
        
        SecureRandom rng=new SecureRandom();
        byte[] salt=new byte[SALT_SIZE];
        rng.nextBytes(salt);
        user.setSalt(salt);

        byte[] hash = SSHA.compute(salt, pwdBytes);
        user.setHash(hash);
    
public synchronized voidupdateUser(java.lang.String name, java.lang.String password, java.lang.String[] groups)
Update data for an existing user. User must exist. This is equivalent to calling removeUser() followed by addUser().

param
name User name.
param
password Cleartext password for the user.
param
groupList List of groups to which user belongs.
throws
BadRealmException If there are problems adding user.
throws
NoSuchUserException If user does not exist.
deprecated

        updateUser(name, name, password, groups);
    
public synchronized voidupdateUser(java.lang.String name, java.lang.String newName, java.lang.String password, java.lang.String[] groups)
Update data for an existing user. User must exist.

param
name Current name of the user to update.
param
newName New name to give this user. It can be the same as the original name. Otherwise it must be a new user name which does not already exist as a user.
param
password Cleartext password for the user. If non-null the user password is changed to this value. If null, the original password is retained.
param
groupList List of groups to which user belongs.
throws
BadRealmException If there are problems adding user.
throws
NoSuchUserException If user does not exist.

                                // user to modify must exist first
        validateUserName(name);
        if (!userTable.containsKey(name)) {
            String msg = sm.getString("filerealm.nouser", name);
            throw new NoSuchUserException(msg);
        }

                                // do general validation
        validateUserName(newName);
        validateGroupList(groups);
        if (password != null) { // null here means re-use previous so is ok
            validatePassword(password);
        }
        
                                // can't duplicate unless modifying itself
        if (!name.equals(newName) && userTable.containsKey(newName)) {
            String msg = sm.getString("filerealm.dupuser", name);
            throw new BadRealmException(msg);
        }

        
        FileRealmUser oldUser = (FileRealmUser)userTable.get(name);
        assert (oldUser != null);
        
                                // create user using new name
        FileRealmUser newUser = new FileRealmUser(newName);
        
                                // set groups as provided by parameter
        changeGroups(oldUser.getGroups(), groups);
        newUser.setGroups(groups);
        
                                // use old password if no new pwd given
        if (password==null) {
            newUser.setSalt(oldUser.getSalt());
            newUser.setHash(oldUser.getHash());
            
        } else {
            setPassword(newUser, password);
        }
        
        userTable.remove(name);
        userTable.put(newName, newUser);
    
public static voidvalidateGroupList(java.lang.String[] groupList)
Validates syntax of a list of group names.

This is equivalent to calling validateGroupName on every element of the groupList.

param
groupList Array of group names to validate.
throws
IASSecurityException Thrown if the value is not valid.

        if (groupList == null || groupList.length == 0) {
            return;             // empty list is ok
        }

        for (int i=0; i<groupList.length; i++) {
            validateGroupName(groupList[i]);
        }
        
    
public static voidvalidateGroupName(java.lang.String group)
Validates syntax of a group name.

This method throws an exception if the provided value is not valid. The message of the exception provides a reason why it is not valid. This is used internally by add/modify User to validate the client-provided values. It is not necessary for the client to call these methods first. However, these are provided as public methods for convenience in case some client (e.g. GUI client) wants to provide independent field validation prior to calling add/modify user.

param
group Group name to validate.
throws
IASSecurityException Thrown if the value is not valid.

        if (group == null || group.length() == 0) {
            String msg = sm.getString("filerealm.nogroup");
            throw new IASSecurityException(msg);
        }

        if (!isValid(group, false)) {
            String msg = sm.getString("filerealm.badchars", group);
            throw new IASSecurityException(msg);
        }
        
        if (!group.equals(group.trim())) {
            String msg = sm.getString("filerealm.badspaces", group);
            throw new IASSecurityException(msg);
        }
    
public static voidvalidatePassword(java.lang.String pwd)
Validates syntax of a password.

This method throws an exception if the provided value is not valid. The message of the exception provides a reason why it is not valid. This is used internally by add/modify User to validate the client-provided values. It is not necessary for the client to call these methods first. However, these are provided as public methods for convenience in case some client (e.g. GUI client) wants to provide independent field validation prior to calling add/modify user.

param
pwd Password to validate.
throws
IASSecurityException Thrown if the value is not valid.

        if (pwd == null) {
            String msg = sm.getString("filerealm.emptypwd");
            throw new IASSecurityException(msg);
        }

        if (!pwd.equals(pwd.trim())) {
            String msg = sm.getString("filerealm.badspacespwd");
            throw new IASSecurityException(msg);
        }
    
public static voidvalidateUserName(java.lang.String name)
Validates syntax of a user name.

This method throws an exception if the provided value is not valid. The message of the exception provides a reason why it is not valid. This is used internally by add/modify User to validate the client-provided values. It is not necessary for the client to call these methods first. However, these are provided as public methods for convenience in case some client (e.g. GUI client) wants to provide independent field validation prior to calling add/modify user.

param
name User name to validate.
throws
IASSecurityException Thrown if the value is not valid.

        if (name == null || name.length() == 0) {
            String msg = sm.getString("filerealm.noname");
            throw new IASSecurityException(msg);
        }

        if (!isValid(name, true)) {
            String msg = sm.getString("filerealm.badname", name);
            throw new IASSecurityException(msg);
        }

        if (!name.equals(name.trim())) {
            String msg = sm.getString("filerealm.badspaces", name);
            throw new IASSecurityException(msg);
        }
    
public voidwriteKeyFile(java.lang.String filename)
Write keyfile data out to disk. The file generation is sychronized within this class only, caller is responsible for any other file locking or revision management as deemed necessary.

param
filename The name of the output file to create.
throws
IOException If write fails.

        synchronized(FileRealm.class) {
            FileOutputStream out = null;
            try {
                out = new FileOutputStream(filename);

                Iterator names = userTable.keySet().iterator();
                while (names.hasNext()) {
                
                    String name = (String)names.next();
                    FileRealmUser ud = (FileRealmUser)userTable.get(name);

                    String entry = encodeUser(name, ud);
                    out.write(entry.getBytes());
                }
            } catch (IOException e) {
                throw e;

            } catch (Exception e) {
                String msg = sm.getString("filerealm.badwrite", e.toString());
                throw new IOException(msg);
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        }

        _logger.fine("Done writing "+filename);