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

org.apache.openejb.core.security.jaas.SQLLoginModule Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.openejb.core.security.jaas;

import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.spi.ContainerSystem;
import org.apache.openejb.util.Base64;
import org.apache.openejb.util.HexConverter;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.Strings;

import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.sql.DataSource;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * A login module that loads security information from a SQL database.  Expects
 * to be run by a GenericSecurityRealm (doesn't work on its own).
 * 

* This requires database connectivity information (either 1: a dataSourceName and * optional dataSourceApplication or 2: a JDBC driver, URL, username, and password) * and 2 SQL queries. *

* The userSelect query should return 2 values, the username and the password in * that order. It should include one PreparedStatement parameter (a ?) which * will be filled in with the username. In other words, the query should look * like: SELECT user, password FROM credentials WHERE username=? *

* The groupSelect query should return 2 values, the username and the group name in * that order (but it may return multiple rows, one per group). It should include * one PreparedStatement parameter (a ?) which will be filled in with the username. * In other words, the query should look like: * SELECT user, role FROM user_roles WHERE username=? *

* This login module checks security credentials so the lifecycle methods must return true to indicate success * or throw LoginException to indicate failure. * * @version $Rev: 1165146 $ $Date: 2011-09-04 19:07:54 -0700 (Sun, 04 Sep 2011) $ */ public class SQLLoginModule implements LoginModule { private static Logger log = Logger.getInstance( LogCategory.OPENEJB_SECURITY, "org.apache.openejb.util.resources"); private EnumMap optionsMap = new EnumMap(Option.class); private String connectionURL; private Properties properties; private Driver driver; private DataSource dataSource; private String userSelect; private String groupSelect; private String digest; private String encoding; private boolean loginSucceeded; private Subject subject; private CallbackHandler handler; private String cbUsername; private String cbPassword; private final Set groups = new HashSet(); private final Set allPrincipals = new HashSet(); public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.handler = callbackHandler; for (Object key : options.keySet()) { Option option = Option.findByName((String) key); if (option != null) { String value = (String) options.get(key); optionsMap.put(option, value.trim()); } else { log.warning("Ignoring option: {0}. Not supported.", key); } } userSelect = optionsMap.get(Option.USER_SELECT); groupSelect = optionsMap.get(Option.GROUP_SELECT); digest = optionsMap.get(Option.DIGEST); encoding = optionsMap.get(Option.ENCODING); if (!Strings.checkNullBlankString(digest)) { // Check if the digest algorithm is available try { MessageDigest.getInstance(digest); } catch (NoSuchAlgorithmException e) { initError(e, "Digest algorithm %s is not available.", digest); } if (encoding != null && !"hex".equalsIgnoreCase(encoding) && !"base64".equalsIgnoreCase(encoding)) { initError(null, "Digest Encoding %s is not supported.", encoding); } } if (optionsMap.containsKey(Option.DATABASE_POOL_NAME)) { String dataSourceName = optionsMap.get(Option.DATABASE_POOL_NAME); ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class); try { dataSource = (DataSource) containerSystem.getJNDIContext().lookup("openejb/Resource/" + dataSourceName); } catch (NamingException e) { initError(e, "Data source %s not found.", dataSourceName); } } else if (optionsMap.containsKey(Option.CONNECTION_URL)) { connectionURL = optionsMap.get(Option.CONNECTION_URL); String user = optionsMap.get(Option.USER); String password = optionsMap.get(Option.PASSWORD); String driverName = optionsMap.get(Option.DRIVER); properties = new Properties(); if (user != null) { properties.put("user", user); } if (password != null) { properties.put("password", password); } if (driverName != null) { ClassLoader cl = getClass().getClassLoader(); try { driver = (Driver) cl.loadClass(driverName).newInstance(); } catch (ClassNotFoundException e) { initError(e, "Driver class %s is not available. Perhaps you need to add it as a dependency in your deployment plan?", driverName); } catch (Exception e) { initError(e, "Unable to load, instantiate, register driver %s: %s", driverName, e.getMessage()); } } } else { initError(null, "Neither %s nor %s was specified", Option.DATABASE_POOL_NAME.name, Option.CONNECTION_URL.name); } } private void initError(Exception e, String format, Object... args) { String message = String.format(format, args); log.error("Initialization failed. {0}", message); throw new IllegalArgumentException(message, e); } /** * This LoginModule is not to be ignored. So, this method should never * return false. * * @return true if authentication succeeds, or throw a LoginException such * as FailedLoginException if authentication fails */ public boolean login() throws LoginException { loginSucceeded = false; Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("User name"); callbacks[1] = new PasswordCallback("Password", false); try { handler.handle(callbacks); } catch (IOException ioe) { throw (LoginException) new LoginException().initCause(ioe); } catch (UnsupportedCallbackException uce) { throw (LoginException) new LoginException().initCause(uce); } assert callbacks.length == 2; cbUsername = ((NameCallback) callbacks[0]).getName(); if (Strings.checkNullBlankString(cbUsername)) { throw new FailedLoginException(); } char[] provided = ((PasswordCallback) callbacks[1]).getPassword(); cbPassword = provided == null ? null : new String(provided); try { Connection conn; if (dataSource != null) { conn = dataSource.getConnection(); } else if (driver != null) { conn = driver.connect(connectionURL, properties); } else { conn = DriverManager.getConnection(connectionURL, properties); } try { PreparedStatement statement = conn.prepareStatement(userSelect); try { int count = statement.getParameterMetaData().getParameterCount(); for (int i = 0; i < count; i++) { statement.setObject(i + 1, cbUsername); } ResultSet result = statement.executeQuery(); try { boolean found = false; while (result.next()) { String userName = result.getString(1); String userPassword = result.getString(2); if (cbUsername.equals(userName)) { found = true; if (!checkPassword(userPassword, cbPassword)) { throw new FailedLoginException(); } break; } } if (!found) { // User does not exist throw new FailedLoginException(); } } finally { result.close(); } } finally { statement.close(); } statement = conn.prepareStatement(groupSelect); try { int count = statement.getParameterMetaData().getParameterCount(); for (int i = 0; i < count; i++) { statement.setObject(i + 1, cbUsername); } ResultSet result = statement.executeQuery(); try { while (result.next()) { String userName = result.getString(1); String groupName = result.getString(2); if (cbUsername.equals(userName)) { groups.add(groupName); } } } finally { result.close(); } } finally { statement.close(); } } finally { conn.close(); } } catch (LoginException e) { // Clear out the private state cbUsername = null; cbPassword = null; groups.clear(); throw e; } catch (SQLException sqle) { // Clear out the private state cbUsername = null; cbPassword = null; groups.clear(); throw (LoginException) new LoginException("SQL error").initCause(sqle); } catch (Exception e) { // Clear out the private state cbUsername = null; cbPassword = null; groups.clear(); throw (LoginException) new LoginException("Could not access datasource").initCause(e); } loginSucceeded = true; return true; } /** * @return true if login succeeded and commit succeeded, or false if login * failed but commit succeeded. * @throws LoginException if login succeeded but commit failed. */ public boolean commit() throws LoginException { if (loginSucceeded) { if (cbUsername != null) { allPrincipals.add(new UserPrincipal(cbUsername)); } for (String group : groups) { allPrincipals.add(new GroupPrincipal(group)); } subject.getPrincipals().addAll(allPrincipals); } // Clear out the private state cbUsername = null; cbPassword = null; groups.clear(); return loginSucceeded; } public boolean abort() throws LoginException { if (loginSucceeded) { // Clear out the private state cbUsername = null; cbPassword = null; groups.clear(); allPrincipals.clear(); } return loginSucceeded; } public boolean logout() throws LoginException { // Clear out the private state loginSucceeded = false; cbUsername = null; cbPassword = null; groups.clear(); if (!subject.isReadOnly()) { // Remove principals added by this LoginModule subject.getPrincipals().removeAll(allPrincipals); } allPrincipals.clear(); return true; } /** * This method checks if the provided password is correct. The original * password may have been digested. * * @param real Original password in digested form if applicable * @param provided User provided password in clear text * @return true If the password is correct */ private boolean checkPassword(String real, String provided) { if (real == null && provided == null) { return true; } if (real == null || provided == null) { return false; } // Both are non-null if (Strings.checkNullBlankString(digest)) { // No digest algorithm is used return real.equals(provided); } try { // Digest the user provided password MessageDigest md = MessageDigest.getInstance(digest); byte[] data = md.digest(provided.getBytes()); if (encoding == null || "hex".equalsIgnoreCase(encoding)) { return real.equalsIgnoreCase(HexConverter.bytesToHex(data)); } else if ("base64".equalsIgnoreCase(encoding)) { return real.equals(new String(Base64.encodeBase64(data))); } } catch (NoSuchAlgorithmException e) { // Should not occur. Availability of algorithm has been checked at initialization log.error("Should not occur. Availability of algorithm has been checked at initialization.", e); } return false; } private enum Option { USER_SELECT("userSelect"), GROUP_SELECT("groupSelect"), CONNECTION_URL("jdbcURL"), USER("jdbcUser"), PASSWORD("jdbcPassword"), DRIVER("jdbcDriver"), DATABASE_POOL_NAME("dataSourceName"), DIGEST("digest"), ENCODING("encoding"); public final String name; private Option(String name) { this.name = name; } public static Option findByName(String name) { for (Option opt : values()) { if (opt.name.equals(name)) return opt; } return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy