Home » geronimo-2.2-source-release » org.apache.geronimo.security.realm.providers » [javadoc | source]

    1   /**
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    */
   17   
   18   package org.apache.geronimo.security.realm.providers;
   19   
   20   import java.io.IOException;
   21   import java.io.InputStream;
   22   import java.net.URI;
   23   import java.security.MessageDigest;
   24   import java.security.NoSuchAlgorithmException;
   25   import java.security.Principal;
   26   import java.util.Arrays;
   27   import java.util.Collections;
   28   import java.util.Enumeration;
   29   import java.util.HashMap;
   30   import java.util.HashSet;
   31   import java.util.List;
   32   import java.util.Map;
   33   import java.util.Properties;
   34   import java.util.Set;
   35   
   36   import javax.security.auth.Subject;
   37   import javax.security.auth.callback.Callback;
   38   import javax.security.auth.callback.CallbackHandler;
   39   import javax.security.auth.callback.NameCallback;
   40   import javax.security.auth.callback.PasswordCallback;
   41   import javax.security.auth.callback.UnsupportedCallbackException;
   42   import javax.security.auth.login.FailedLoginException;
   43   import javax.security.auth.login.LoginException;
   44   import javax.security.auth.spi.LoginModule;
   45   
   46   import org.slf4j.Logger;
   47   import org.slf4j.LoggerFactory;
   48   import org.apache.geronimo.common.GeronimoSecurityException;
   49   import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
   50   import org.apache.geronimo.security.jaas.WrappingLoginModule;
   51   import org.apache.geronimo.system.serverinfo.ServerInfo;
   52   import org.apache.geronimo.crypto.SimpleEncryption;
   53   import org.apache.geronimo.crypto.EncryptionManager;
   54   import org.apache.geronimo.crypto.encoders.Base64;
   55   import org.apache.geronimo.crypto.encoders.HexTranslator;
   56   
   57   
   58   /**
   59    * A LoginModule that reads a list of credentials and group from files on disk.  The
   60    * files should be formatted using standard Java properties syntax.  Expects
   61    * to be run by a GenericSecurityRealm (doesn't work on its own).
   62    * <p/>
   63    * This login module checks security credentials so the lifecycle methods must return true to indicate success
   64    * or throw LoginException to indicate failure.
   65    *
   66    * @version $Rev: 653740 $ $Date: 2008-05-06 03:44:18 -0700 (Tue, 06 May 2008) $
   67    */
   68   public class PropertiesFileLoginModule implements LoginModule {
   69       public final static String USERS_URI = "usersURI";
   70       public final static String GROUPS_URI = "groupsURI";
   71       public final static String DIGEST = "digest";
   72       public final static String ENCODING = "encoding";
   73       public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(USERS_URI, GROUPS_URI, DIGEST, ENCODING));
   74   
   75       private static final Logger log = LoggerFactory.getLogger(PropertiesFileLoginModule.class);
   76       
   77       final Properties users = new Properties();
   78       final Map<String, Set<String>> groups = new HashMap<String, Set<String>>();
   79       private String digest;
   80       private String encoding;
   81   
   82       private boolean loginSucceeded;
   83       private Subject subject;
   84       private CallbackHandler handler;
   85       private String username;
   86       private String password;
   87       private final Set<Principal> allPrincipals = new HashSet<Principal>();
   88   
   89       public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
   90           this.subject = subject;
   91           this.handler = callbackHandler;
   92           for(Object option: options.keySet()) {
   93               if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option)
   94                       && !WrappingLoginModule.supportedOptions.contains(option)) {
   95                   log.warn("Ignoring option: "+option+". Not supported.");
   96               }
   97           }
   98           try {
   99               ServerInfo serverInfo = (ServerInfo) options.get(JaasLoginModuleUse.SERVERINFO_LM_OPTION);
  100               final String users = (String) options.get(USERS_URI);
  101               final String groups = (String) options.get(GROUPS_URI);
  102               digest = (String) options.get(DIGEST);
  103               encoding = (String) options.get(ENCODING);
  104   
  105               if (digest != null && !digest.equals("")) {
  106                   // Check if the digest algorithm is available
  107                   try {
  108                       MessageDigest.getInstance(digest);
  109                   } catch (NoSuchAlgorithmException e) {
  110                       log.error("Initialization failed. Digest algorithm " + digest + " is not available.", e);
  111                       throw new IllegalArgumentException(
  112                               "Unable to configure properties file login module: " + e.getMessage(), e);
  113                   }
  114                   if (encoding != null && !"hex".equalsIgnoreCase(encoding) && !"base64".equalsIgnoreCase(encoding)) {
  115                       log.error("Initialization failed. Digest Encoding " + encoding + " is not supported.");
  116                       throw new IllegalArgumentException(
  117                               "Unable to configure properties file login module. Digest Encoding " + encoding + " not supported.");
  118                   }
  119               }
  120               if (users == null || groups == null) {
  121                   throw new IllegalArgumentException("Both " + USERS_URI + " and " + GROUPS_URI + " must be provided!");
  122               }
  123               URI usersURI = new URI(users);
  124               URI groupsURI = new URI(groups);
  125               loadProperties(serverInfo, usersURI, groupsURI);
  126           } catch (Exception e) {
  127               log.error("Initialization failed", e);
  128               throw new IllegalArgumentException("Unable to configure properties file login module: " + e.getMessage(),
  129                       e);
  130           }
  131       }
  132   
  133       public void loadProperties(ServerInfo serverInfo, URI userURI, URI groupURI) throws GeronimoSecurityException {
  134           try {
  135               URI userFile = serverInfo.resolveServer(userURI);
  136               URI groupFile = serverInfo.resolveServer(groupURI);
  137               InputStream stream = userFile.toURL().openStream();
  138               users.clear();
  139               users.load(stream);
  140               stream.close();
  141   
  142               Properties temp = new Properties();
  143               stream = groupFile.toURL().openStream();
  144               temp.load(stream);
  145               stream.close();
  146   
  147               Enumeration e = temp.keys();
  148               while (e.hasMoreElements()) {
  149                   String groupName = (String) e.nextElement();
  150                   String[] userList = ((String) temp.get(groupName)).split(",");
  151   
  152                   Set<String> userset = groups.get(groupName);
  153                   if (userset == null) {
  154                       userset = new HashSet<String>();
  155                       groups.put(groupName, userset);
  156                   }
  157                   for (String user : userList) {
  158                       userset.add(user);
  159                   }
  160               }
  161   
  162           } catch (Exception e) {
  163               log.error("Properties File Login Module - data load failed", e);
  164               throw new GeronimoSecurityException(e);
  165           }
  166       }
  167   
  168   
  169       /**
  170        * This LoginModule is not to be ignored.  So, this method should never return false.
  171        * @return true if authentication succeeds, or throw a LoginException such as FailedLoginException
  172        *         if authentication fails
  173        */
  174       public boolean login() throws LoginException {
  175           loginSucceeded = false;
  176           Callback[] callbacks = new Callback[2];
  177   
  178           callbacks[0] = new NameCallback("User name");
  179           callbacks[1] = new PasswordCallback("Password", false);
  180           try {
  181               handler.handle(callbacks);
  182           } catch (IOException ioe) {
  183               throw (LoginException) new LoginException().initCause(ioe);
  184           } catch (UnsupportedCallbackException uce) {
  185               throw (LoginException) new LoginException().initCause(uce);
  186           }
  187           assert callbacks.length == 2;
  188           username = ((NameCallback) callbacks[0]).getName();
  189           if (username == null || username.equals("")) {
  190               // Clear out the private state
  191               username = null;
  192               password = null;
  193               throw new FailedLoginException();
  194           }
  195           String realPassword = users.getProperty(username);
  196           // Decrypt the password if needed, so we can compare it with the supplied one
  197           if (realPassword != null) {
  198               realPassword = (String) EncryptionManager.decrypt(realPassword);
  199           }
  200           char[] entered = ((PasswordCallback) callbacks[1]).getPassword();
  201           password = entered == null ? null : new String(entered);
  202           if (!checkPassword(realPassword, password)) {
  203               // Clear out the private state
  204               username = null;
  205               password = null;
  206               throw new FailedLoginException();
  207           }
  208   
  209           loginSucceeded = true;
  210           return true;
  211       }
  212   
  213       /*
  214        * @exception LoginException if login succeeded but commit failed.
  215        *
  216        * @return true if login succeeded and commit succeeded, or false if login failed but commit succeeded.
  217        */
  218       public boolean commit() throws LoginException {
  219           if(loginSucceeded) {
  220               if(username != null) {
  221                   allPrincipals.add(new GeronimoUserPrincipal(username));
  222               }
  223               for (Map.Entry<String, Set<String>> entry : groups.entrySet()) {
  224                   String groupName = entry.getKey();
  225                   Set<String> users = entry.getValue();
  226                   for (String user : users) {
  227                       if (username.equals(user)) {
  228                           allPrincipals.add(new GeronimoGroupPrincipal(groupName));
  229                           break;
  230                       }
  231                   }
  232               }
  233               subject.getPrincipals().addAll(allPrincipals);
  234           }
  235           // Clear out the private state
  236           username = null;
  237           password = null;
  238   
  239           return loginSucceeded;
  240       }
  241   
  242       public boolean abort() throws LoginException {
  243           if(loginSucceeded) {
  244               // Clear out the private state
  245               username = null;
  246               password = null;
  247               allPrincipals.clear();
  248           }
  249           return loginSucceeded;
  250       }
  251   
  252       public boolean logout() throws LoginException {
  253           // Clear out the private state
  254           loginSucceeded = false;
  255           username = null;
  256           password = null;
  257           if(!subject.isReadOnly()) {
  258               // Remove principals added by this LoginModule
  259               subject.getPrincipals().removeAll(allPrincipals);
  260           }
  261           allPrincipals.clear();
  262           return true;
  263       }
  264   
  265       /**
  266        * This method checks if the provided password is correct.  The original password may have been digested.
  267        *
  268        * @param real     Original password in digested form if applicable
  269        * @param provided User provided password in clear text
  270        * @return true     If the password is correct
  271        */
  272       private boolean checkPassword(String real, String provided) {
  273           if (real == null && provided == null) {
  274               return true;
  275           }
  276           if (real == null || provided == null) {
  277               return false;
  278           }
  279   
  280           //both non-null
  281           if (digest == null || digest.equals("")) {
  282               // No digest algorithm is used
  283               return real.equals(provided);
  284           }
  285           try {
  286               // Digest the user provided password
  287               MessageDigest md = MessageDigest.getInstance(digest);
  288               byte[] data = md.digest(provided.getBytes());
  289               if (encoding == null || "hex".equalsIgnoreCase(encoding)) {
  290                   // Convert bytes to hex digits
  291                   byte[] hexData = new byte[data.length * 2];
  292                   HexTranslator ht = new HexTranslator();
  293                   ht.encode(data, 0, data.length, hexData, 0);
  294                   // Compare the digested provided password with the actual one
  295                   return real.equalsIgnoreCase(new String(hexData));
  296               } else if ("base64".equalsIgnoreCase(encoding)) {
  297                   return real.equals(new String(Base64.encode(data)));
  298               }
  299           } catch (NoSuchAlgorithmException e) {
  300               // Should not occur.  Availability of algorithm has been checked at initialization
  301               log.error("Should not occur.  Availability of algorithm has been checked at initialization.", e);
  302           }
  303           return false;
  304       }
  305   }

Home » geronimo-2.2-source-release » org.apache.geronimo.security.realm.providers » [javadoc | source]