All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.security.auth.realm.file.FileRealm Maven / Gradle / Ivy

There is a newer version: 10.0-b28
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */


package com.sun.enterprise.security.auth.realm.file;

import java.util.*;
import java.util.logging.Level;
import java.io.*;
import java.security.*;
import javax.security.auth.login.*;
import com.sun.enterprise.security.auth.realm.User;
import com.sun.enterprise.security.auth.realm.Realm;
import com.sun.enterprise.security.auth.realm.BadRealmException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;
import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
import com.sun.enterprise.security.util.*;
import com.sun.enterprise.security.auth.realm.IASRealm;
import org.jvnet.hk2.annotations.Service;


/**
 * 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 */ @Service final public class FileRealm extends IASRealm { // Descriptive string of the authentication type of this realm. public static final String AUTH_TYPE = "filepassword"; // These are property names which should be in auth-realm in server.xml public static final String PARAM_KEYFILE="file"; // Separators in keyfile (user;pwd-info;group[,group]*) private static final String FIELD_SEP=";"; private static final String GROUP_SEP=","; private static final String COMMENT="#"; // Valid non-alphanumeric/whitespace chars in user/group name public static final String MISC_VALID_CHARS="_-."; // Number of bytes of salt for SSHA private static final int SALT_SIZE=8; // Contains cache of keyfile data private Map userTable; // user=>FileRealmUser private Hashtable groupSizeMap; // maps of groups with value cardinality of group private boolean constructed = false; /** * 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. * */ public FileRealm(String keyfile) throws BadRealmException, NoSuchRealmException { 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); } /** * Constructor. * *

Do not use directly. */ public FileRealm() { } /** * 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. * */ protected void init(Properties props) throws BadRealmException, NoSuchRealmException { 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(); } /** * 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. */ public String getAuthType() { return AUTH_TYPE; } /** * Returns names of all the users in this particular realm. * * @return enumeration of user names (strings) * @exception BadRealmException if realm data structures are bad */ public Enumeration getUserNames() throws BadRealmException { return (new Vector(userTable.keySet())).elements(); // ugh } /** * 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. */ public User getUser(String name) throws NoSuchUserException { FileRealmUser u = (FileRealmUser)userTable.get(name); if (u == null) { String msg = sm.getString("filerealm.nouser", name); throw new NoSuchUserException(msg); } return u; } /** * 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 */ public Enumeration getGroupNames() throws BadRealmException { return groupSizeMap.keys(); } /** * 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. */ public Enumeration getGroupNames(String username) throws NoSuchUserException { 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(); } /** * 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 * */ public void refresh() throws BadRealmException { 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()); } } /** * 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. * */ public String[] authenticate(String user, String password) { 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; } //--------------------------------------------------------------------- // File realm maintenance methods for admin. /** * 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 * [email protected]. 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 * */ private static boolean isValid(String s, boolean userName) { for (int i=0; iThis 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. * */ public static void validateUserName(String name) throws IASSecurityException { 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); } } /** * 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. * */ public static void validatePassword(String pwd) throws IASSecurityException { 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); } } /** * 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. * */ public static void validateGroupName(String group) throws IASSecurityException { 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); } } /** * 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. * * */ public static void validateGroupList(String[] groupList) throws IASSecurityException { if (groupList == null || groupList.length == 0) { return; // empty list is ok } for (int i=0; i 0) { groupSizeMap.put(groupList[i], new Integer(gpSize)); } else { groupSizeMap.remove(groupList[i]); } } } } } /** * This method update the internal group list. */ private void changeGroups(String[] oldGroupList, String[] newGroupList) { addGroupNames(newGroupList); reduceGroups(oldGroupList); } /** * Load keyfile from config and populate internal cache. * */ private void loadKeyFile() throws BadRealmException { 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) { } } } } /** * 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. * */ private static String encodeUser(String name, FileRealmUser ud) { 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(); } /** * 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. * */ private static FileRealmUser decodeUser(String encodedLine, Map newGroupSizeMap) throws IASSecurityException { 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; } /** * 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. * */ private static FileRealmUser createNewUser(String name, String pwd, String[] groups) throws IASSecurityException { FileRealmUser ud = new FileRealmUser(name); if (groups == null) { groups = new String[0]; } ud.setGroups(groups); setPassword(ud, pwd); return ud; } /** * 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. * */ private static void setPassword(FileRealmUser user, String pwd) throws IASSecurityException { 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); } /** * Test method. Not for production use. * */ public static void main(String[] args) { 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





© 2015 - 2025 Weber Informatics LLC | Privacy Policy