Home » openejb-3.1.2-src » org.apache » openejb » core » security » jaas » [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.openejb.core.security.jaas;
   19   
   20   import org.apache.openejb.loader.SystemInstance;
   21   import org.apache.openejb.spi.ContainerSystem;
   22   import org.apache.openejb.util.Base64;
   23   import org.apache.openejb.util.HexConverter;
   24   import org.apache.openejb.util.LogCategory;
   25   import org.apache.openejb.util.Logger;
   26   import org.apache.openejb.util.StringUtilities;
   27   
   28   import javax.naming.NamingException;
   29   import javax.security.auth.Subject;
   30   import javax.security.auth.callback.Callback;
   31   import javax.security.auth.callback.CallbackHandler;
   32   import javax.security.auth.callback.NameCallback;
   33   import javax.security.auth.callback.PasswordCallback;
   34   import javax.security.auth.callback.UnsupportedCallbackException;
   35   import javax.security.auth.login.FailedLoginException;
   36   import javax.security.auth.login.LoginException;
   37   import javax.security.auth.spi.LoginModule;
   38   import javax.sql.DataSource;
   39   import java.io.IOException;
   40   import java.security.MessageDigest;
   41   import java.security.NoSuchAlgorithmException;
   42   import java.security.Principal;
   43   import java.sql.Connection;
   44   import java.sql.Driver;
   45   import java.sql.DriverManager;
   46   import java.sql.PreparedStatement;
   47   import java.sql.ResultSet;
   48   import java.sql.SQLException;
   49   import java.util.EnumMap;
   50   import java.util.HashSet;
   51   import java.util.Map;
   52   import java.util.Properties;
   53   import java.util.Set;
   54   
   55   /**
   56    * A login module that loads security information from a SQL database.  Expects
   57    * to be run by a GenericSecurityRealm (doesn't work on its own).
   58    * <p/>
   59    * This requires database connectivity information (either 1: a dataSourceName and
   60    * optional dataSourceApplication or 2: a JDBC driver, URL, username, and password)
   61    * and 2 SQL queries.
   62    * <p/>
   63    * The userSelect query should return 2 values, the username and the password in
   64    * that order.  It should include one PreparedStatement parameter (a ?) which
   65    * will be filled in with the username.  In other words, the query should look
   66    * like: <tt>SELECT user, password FROM credentials WHERE username=?</tt>
   67    * <p/>
   68    * The groupSelect query should return 2 values, the username and the group name in
   69    * that order (but it may return multiple rows, one per group).  It should include
   70    * one PreparedStatement parameter (a ?) which will be filled in with the username.
   71    * In other words, the query should look like:
   72    * <tt>SELECT user, role FROM user_roles WHERE username=?</tt>
   73    * <p/>
   74    * This login module checks security credentials so the lifecycle methods must return true to indicate success
   75    * or throw LoginException to indicate failure.
   76    *
   77    * @version $Rev: 710022 $ $Date: 2008-11-03 00:40:14 -0800 (Mon, 03 Nov 2008) $
   78    */
   79   public class SQLLoginModule implements LoginModule {
   80       private static Logger log = Logger.getInstance(
   81               LogCategory.OPENEJB_SECURITY, "org.apache.openejb.util.resources");
   82   
   83       private EnumMap<Option, String> optionsMap = new EnumMap<Option, String>(Option.class);
   84       private String connectionURL;
   85       private Properties properties;
   86       private Driver driver;
   87       private DataSource dataSource;
   88       private String userSelect;
   89       private String groupSelect;
   90       private String digest;
   91       private String encoding;
   92   
   93       private boolean loginSucceeded;
   94       private Subject subject;
   95       private CallbackHandler handler;
   96       private String cbUsername;
   97       private String cbPassword;
   98       private final Set<String> groups = new HashSet<String>();
   99       private final Set<Principal> allPrincipals = new HashSet<Principal>();
  100   
  101       public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
  102           this.subject = subject;
  103           this.handler = callbackHandler;
  104   
  105           for (Object key : options.keySet()) {
  106               Option option = Option.findByName((String) key);
  107               if (option != null) {
  108                   String value = (String) options.get(key);
  109                   optionsMap.put(option, value.trim());
  110               } else {
  111                   log.warning("Ignoring option: {0}. Not supported.", key);
  112               }
  113           }
  114   
  115           userSelect = optionsMap.get(Option.USER_SELECT);
  116           groupSelect = optionsMap.get(Option.GROUP_SELECT);
  117   
  118           digest = optionsMap.get(Option.DIGEST);
  119           encoding = optionsMap.get(Option.ENCODING);
  120   
  121           if (!StringUtilities.checkNullBlankString(digest)) {
  122               // Check if the digest algorithm is available
  123               try {
  124                   MessageDigest.getInstance(digest);
  125               } catch (NoSuchAlgorithmException e) {
  126                   initError(e, "Digest algorithm %s is not available.", digest);
  127               }
  128   
  129               if (encoding != null && !"hex".equalsIgnoreCase(encoding) && !"base64".equalsIgnoreCase(encoding)) {
  130                   initError(null, "Digest Encoding %s is not supported.", encoding);
  131               }
  132           }
  133   
  134           if (optionsMap.containsKey(Option.DATABASE_POOL_NAME)) {
  135               String dataSourceName = optionsMap.get(Option.DATABASE_POOL_NAME);
  136               ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
  137               try {
  138                   dataSource = (DataSource) containerSystem.getJNDIContext().lookup("openejb/Resource/" + dataSourceName);
  139               } catch (NamingException e) {
  140                   initError(e, "Data source %s not found.", dataSourceName);
  141               }
  142           } else if (optionsMap.containsKey(Option.CONNECTION_URL)) {
  143               connectionURL = optionsMap.get(Option.CONNECTION_URL);
  144               String user = optionsMap.get(Option.USER);
  145               String password = optionsMap.get(Option.PASSWORD);
  146               String driverName = optionsMap.get(Option.DRIVER);
  147               properties = new Properties();
  148   
  149               if (user != null) {
  150                   properties.put("user", user);
  151               }
  152   
  153               if (password != null) {
  154                   properties.put("password", password);
  155               }
  156   
  157               if (driverName != null) {
  158                   ClassLoader cl = getClass().getClassLoader();
  159                   try {
  160                       driver = (Driver) cl.loadClass(driverName).newInstance();
  161                   } catch (ClassNotFoundException e) {
  162                       initError(e, "Driver class %s is not available. Perhaps you need to add it as a dependency in your deployment plan?", driverName);
  163                   } catch (Exception e) {
  164                       initError(e, "Unable to load, instantiate, register driver %s: %s", driverName, e.getMessage());
  165                   }
  166               }
  167           } else {
  168               initError(null, "Neither %s nor %s was specified", Option.DATABASE_POOL_NAME.name, Option.CONNECTION_URL.name);
  169           }
  170       }
  171   
  172       private void initError(Exception e, String format, Object... args) {
  173           String message = String.format(format, args);
  174           log.error("Initialization failed. {0}", message);
  175           throw new IllegalArgumentException(message, e);
  176       }
  177   
  178       /**
  179        * This LoginModule is not to be ignored. So, this method should never
  180        * return false.
  181        *
  182        * @return true if authentication succeeds, or throw a LoginException such
  183        *         as FailedLoginException if authentication fails
  184        */
  185       public boolean login() throws LoginException {
  186           loginSucceeded = false;
  187           Callback[] callbacks = new Callback[2];
  188   
  189           callbacks[0] = new NameCallback("User name");
  190           callbacks[1] = new PasswordCallback("Password", false);
  191   
  192           try {
  193               handler.handle(callbacks);
  194           } catch (IOException ioe) {
  195               throw (LoginException) new LoginException().initCause(ioe);
  196           } catch (UnsupportedCallbackException uce) {
  197               throw (LoginException) new LoginException().initCause(uce);
  198           }
  199   
  200           assert callbacks.length == 2;
  201   
  202           cbUsername = ((NameCallback) callbacks[0]).getName();
  203   
  204           if (StringUtilities.checkNullBlankString(cbUsername)) {
  205               throw new FailedLoginException();
  206           }
  207   
  208           char[] provided = ((PasswordCallback) callbacks[1]).getPassword();
  209           cbPassword = provided == null ? null : new String(provided);
  210   
  211           try {
  212               Connection conn;
  213               if (dataSource != null) {
  214                   conn = dataSource.getConnection();
  215               } else if (driver != null) {
  216                   conn = driver.connect(connectionURL, properties);
  217               } else {
  218                   conn = DriverManager.getConnection(connectionURL, properties);
  219               }
  220   
  221               try {
  222                   PreparedStatement statement = conn.prepareStatement(userSelect);
  223                   try {
  224                       int count = statement.getParameterMetaData().getParameterCount();
  225                       for (int i = 0; i < count; i++) {
  226                           statement.setObject(i + 1, cbUsername);
  227                       }
  228                       ResultSet result = statement.executeQuery();
  229   
  230                       try {
  231                           boolean found = false;
  232                           while (result.next()) {
  233                               String userName = result.getString(1);
  234                               String userPassword = result.getString(2);
  235   
  236                               if (cbUsername.equals(userName)) {
  237                                   found = true;
  238                                   if (!checkPassword(userPassword, cbPassword)) {
  239                                       throw new FailedLoginException();
  240                                   }
  241                                   break;
  242                               }
  243                           }
  244                           if (!found) {
  245                               // User does not exist
  246                               throw new FailedLoginException();
  247                           }
  248                       } finally {
  249                           result.close();
  250                       }
  251                   } finally {
  252                       statement.close();
  253                   }
  254   
  255                   statement = conn.prepareStatement(groupSelect);
  256                   try {
  257                       int count = statement.getParameterMetaData().getParameterCount();
  258                       for (int i = 0; i < count; i++) {
  259                           statement.setObject(i + 1, cbUsername);
  260                       }
  261                       ResultSet result = statement.executeQuery();
  262   
  263                       try {
  264                           while (result.next()) {
  265                               String userName = result.getString(1);
  266                               String groupName = result.getString(2);
  267   
  268                               if (cbUsername.equals(userName)) {
  269                                   groups.add(groupName);
  270                               }
  271                           }
  272                       } finally {
  273                           result.close();
  274                       }
  275                   } finally {
  276                       statement.close();
  277                   }
  278               } finally {
  279                   conn.close();
  280               }
  281           } catch (LoginException e) {
  282               // Clear out the private state
  283               cbUsername = null;
  284               cbPassword = null;
  285               groups.clear();
  286               throw e;
  287           } catch (SQLException sqle) {
  288               // Clear out the private state
  289               cbUsername = null;
  290               cbPassword = null;
  291               groups.clear();
  292               throw (LoginException) new LoginException("SQL error").initCause(sqle);
  293           } catch (Exception e) {
  294               // Clear out the private state
  295               cbUsername = null;
  296               cbPassword = null;
  297               groups.clear();
  298               throw (LoginException) new LoginException("Could not access datasource").initCause(e);
  299           }
  300   
  301           loginSucceeded = true;
  302           return true;
  303       }
  304   
  305       /**
  306        * @return true if login succeeded and commit succeeded, or false if login
  307        *         failed but commit succeeded.
  308        * @throws LoginException if login succeeded but commit failed.
  309        */
  310       public boolean commit() throws LoginException {
  311           if (loginSucceeded) {
  312               if (cbUsername != null) {
  313                   allPrincipals.add(new UserPrincipal(cbUsername));
  314               }
  315               for (String group : groups) {
  316                   allPrincipals.add(new GroupPrincipal(group));
  317               }
  318               subject.getPrincipals().addAll(allPrincipals);
  319           }
  320   
  321           // Clear out the private state
  322           cbUsername = null;
  323           cbPassword = null;
  324           groups.clear();
  325   
  326           return loginSucceeded;
  327       }
  328   
  329       public boolean abort() throws LoginException {
  330           if (loginSucceeded) {
  331               // Clear out the private state
  332               cbUsername = null;
  333               cbPassword = null;
  334               groups.clear();
  335               allPrincipals.clear();
  336           }
  337           return loginSucceeded;
  338       }
  339   
  340       public boolean logout() throws LoginException {
  341           // Clear out the private state
  342           loginSucceeded = false;
  343           cbUsername = null;
  344           cbPassword = null;
  345           groups.clear();
  346           if (!subject.isReadOnly()) {
  347               // Remove principals added by this LoginModule
  348               subject.getPrincipals().removeAll(allPrincipals);
  349           }
  350           allPrincipals.clear();
  351           return true;
  352       }
  353   
  354       /**
  355        * This method checks if the provided password is correct. The original
  356        * password may have been digested.
  357        *
  358        * @param real     Original password in digested form if applicable
  359        * @param provided User provided password in clear text
  360        * @return true If the password is correct
  361        */
  362       private boolean checkPassword(String real, String provided) {
  363           if (real == null && provided == null) {
  364               return true;
  365           }
  366   
  367           if (real == null || provided == null) {
  368               return false;
  369           }
  370   
  371           // Both are non-null
  372           if (StringUtilities.checkNullBlankString(digest)) {
  373               // No digest algorithm is used
  374               return real.equals(provided);
  375           }
  376   
  377           try {
  378               // Digest the user provided password
  379               MessageDigest md = MessageDigest.getInstance(digest);
  380               byte[] data = md.digest(provided.getBytes());
  381   
  382               if (encoding == null || "hex".equalsIgnoreCase(encoding)) {
  383                   return real.equalsIgnoreCase(HexConverter.bytesToHex(data));
  384               } else if ("base64".equalsIgnoreCase(encoding)) {
  385                   return real.equals(new String(Base64.encodeBase64(data)));
  386               }
  387           } catch (NoSuchAlgorithmException e) {
  388               // Should not occur.  Availability of algorithm has been checked at initialization
  389               log.error("Should not occur.  Availability of algorithm has been checked at initialization.", e);
  390           }
  391           return false;
  392       }
  393   
  394       private enum Option {
  395           USER_SELECT("userSelect"),
  396           GROUP_SELECT("groupSelect"),
  397           CONNECTION_URL("jdbcURL"),
  398           USER("jdbcUser"),
  399           PASSWORD("jdbcPassword"),
  400           DRIVER("jdbcDriver"),
  401           DATABASE_POOL_NAME("dataSourceName"),
  402           DIGEST("digest"),
  403           ENCODING("encoding");
  404   
  405           public final String name;
  406   
  407           private Option(String name) {
  408               this.name = name;
  409           }
  410   
  411           public static Option findByName(String name) {
  412               for (Option opt : values()) {
  413                   if (opt.name.equals(name))
  414                       return opt;
  415               }
  416               return null;
  417           }
  418   
  419       }
  420   }

Home » openejb-3.1.2-src » org.apache » openejb » core » security » jaas » [javadoc | source]