Package org.apache.geronimo.security.realm.providers

Source Code of org.apache.geronimo.security.realm.providers.SQLLoginModule

/**
*  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.geronimo.security.realm.providers;

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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.geronimo.gbean.AbstractName;
import org.apache.geronimo.gbean.AbstractNameQuery;
import org.apache.geronimo.kernel.GBeanNotFoundException;
import org.apache.geronimo.kernel.Kernel;
import org.apache.geronimo.kernel.KernelRegistry;
import org.apache.geronimo.management.geronimo.JCAManagedConnectionFactory;
import org.apache.geronimo.security.jaas.JaasLoginModuleUse;
import org.apache.geronimo.security.jaas.WrappingLoginModule;
import org.apache.geronimo.crypto.encoders.Base64;
import org.apache.geronimo.crypto.encoders.HexTranslator;

/**
* A login module that loads security information from a SQL database.  Expects
* to be run by a GenericSecurityRealm (doesn't work on its own).
* <p/>
* 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.
* <p/>
* 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: <tt>SELECT user, password FROM credentials WHERE username=?</tt>
* <p/>
* 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:
* <tt>SELECT user, role FROM user_roles WHERE username=?</tt>
* <p/>
* This login module checks security credentials so the lifecycle methods must return true to indicate success
* or throw LoginException to indicate failure.
*
* @version $Rev: 698367 $ $Date: 2008-09-24 05:51:48 +0800 (Wed, 24 Sep 2008) $
*/
public class SQLLoginModule implements LoginModule {
    private static final Logger log = LoggerFactory.getLogger(SQLLoginModule.class);
   
    public final static String USER_SELECT = "userSelect";
    public final static String GROUP_SELECT = "groupSelect";
    public final static String CONNECTION_URL = "jdbcURL";
    public final static String USER = "jdbcUser";
    public final static String PASSWORD = "jdbcPassword";
    public final static String DRIVER = "jdbcDriver";
    public final static String DATABASE_POOL_NAME = "dataSourceName";
    public final static String DATABASE_POOL_APP_NAME = "dataSourceApplication";
    public final static String DIGEST = "digest";
    public final static String ENCODING = "encoding";
    public final static List<String> supportedOptions = Collections.unmodifiableList(Arrays.asList(USER_SELECT, GROUP_SELECT, CONNECTION_URL,
            USER, PASSWORD, DRIVER, DATABASE_POOL_NAME, DATABASE_POOL_APP_NAME, DIGEST, ENCODING));

    private String connectionURL;
    private Properties properties;
    private Driver driver;
    private JCAManagedConnectionFactory factory;
    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<String> groups = new HashSet<String>();
    private final Set<Principal> allPrincipals = new HashSet<Principal>();

    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
        this.subject = subject;
        this.handler = callbackHandler;
        for(Object option: options.keySet()) {
            if(!supportedOptions.contains(option) && !JaasLoginModuleUse.supportedOptions.contains(option)
                    && !WrappingLoginModule.supportedOptions.contains(option)) {
                log.warn("Ignoring option: "+option+". Not supported.");
            }
        }
        userSelect = (String) options.get(USER_SELECT);
        groupSelect = (String) options.get(GROUP_SELECT);

        digest = (String) options.get(DIGEST);
        encoding = (String) options.get(ENCODING);
        if (digest != null && !digest.equals("")) {
            // Check if the digest algorithm is available
            try {
                MessageDigest.getInstance(digest);
            } catch (NoSuchAlgorithmException e) {
                log.error("Initialization failed. Digest algorithm " + digest + " is not available.", e);
                throw new IllegalArgumentException("Unable to configure SQL login module: " + e.getMessage(), e);
            }
            if (encoding != null && !"hex".equalsIgnoreCase(encoding) && !"base64".equalsIgnoreCase(encoding)) {
                log.error("Initialization failed. Digest Encoding " + encoding + " is not supported.");
                throw new IllegalArgumentException(
                        "Unable to configure SQL login module. Digest Encoding " + encoding + " not supported.");
            }
        }

        String dataSourceName = (String) options.get(DATABASE_POOL_NAME);
        if (dataSourceName != null) {
            dataSourceName = dataSourceName.trim();
            String dataSourceAppName = (String) options.get(DATABASE_POOL_APP_NAME);
            if (dataSourceAppName == null || dataSourceAppName.trim().equals("")) {
                dataSourceAppName = "null";
            } else {
                dataSourceAppName = dataSourceAppName.trim();
            }
            String kernelName = (String) options.get(JaasLoginModuleUse.KERNEL_NAME_LM_OPTION);
            Kernel kernel = KernelRegistry.getKernel(kernelName);
            Set<AbstractName> set = kernel.listGBeans(new AbstractNameQuery(JCAManagedConnectionFactory.class.getName()));
            JCAManagedConnectionFactory factory;
            for (AbstractName name : set) {
                if (name.getName().get("J2EEApplication").equals(dataSourceAppName) &&
                        name.getName().get("name").equals(dataSourceName)) {
                    try {
                        factory = (JCAManagedConnectionFactory) kernel.getGBean(name);
                        String type = factory.getConnectionFactoryInterface();
                        if (type.equals(DataSource.class.getName())) {
                            this.factory = factory;
                            break;
                        }
                    } catch (GBeanNotFoundException e) {
                        // ignore... GBean was unregistered
                    }
                }
            }
        } else {
            connectionURL = (String) options.get(CONNECTION_URL);
            properties = new Properties();
            if (options.get(USER) != null) {
                properties.put("user", options.get(USER));
            }
            if (options.get(PASSWORD) != null) {
                properties.put("password", options.get(PASSWORD));
            }
            ClassLoader cl = (ClassLoader) options.get(JaasLoginModuleUse.CLASSLOADER_LM_OPTION);
            try {
                driver = (Driver) cl.loadClass((String) options.get(DRIVER)).newInstance();
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Driver class " + options.get(
                        DRIVER) + " is not available.  Perhaps you need to add it as a dependency in your deployment plan?",
                        e);
            } catch (Exception e) {
                throw new IllegalArgumentException(
                        "Unable to load, instantiate, register driver " + options.get(DRIVER) + ": " + e.getMessage(),
                        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 (cbUsername == null || cbUsername.equals("")) {
            throw new FailedLoginException();
        }
        char[] provided = ((PasswordCallback) callbacks[1]).getPassword();
        cbPassword = provided == null ? null : new String(provided);

        try {
            Connection conn;
            if (factory != null) {
                DataSource ds = (DataSource) factory.getConnectionFactory();
                conn = ds.getConnection();
            } else {
                conn = driver.connect(connectionURL, properties);
            }

            try {
                PreparedStatement statement = conn.prepareStatement(userSelect);
                try {
                    int count = countParameters(userSelect);
                    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 = countParameters(groupSelect);
                    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;
    }

    /*
     * @exception LoginException if login succeeded but commit failed.
     *
     * @return true if login succeeded and commit succeeded, or false if login failed but commit succeeded.
     */
    public boolean commit() throws LoginException {
        if(loginSucceeded) {
            if(cbUsername != null) {
                allPrincipals.add(new GeronimoUserPrincipal(cbUsername));
            }
            for(String group: groups) {
                allPrincipals.add(new GeronimoGroupPrincipal(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;
    }

    private static int countParameters(String sql) {
        int count = 0;
        int pos = -1;
        while ((pos = sql.indexOf('?', pos + 1)) != -1) {
            ++count;
        }
        return count;
    }

    /**
     * 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 (digest == null || digest.equals("")) {
            // 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)) {
                // Convert bytes to hex digits
                byte[] hexData = new byte[data.length * 2];
                HexTranslator ht = new HexTranslator();
                ht.encode(data, 0, data.length, hexData, 0);
                // Compare the digested provided password with the actual one
                return real.equalsIgnoreCase(new String(hexData));
            } else if ("base64".equalsIgnoreCase(encoding)) {
                return real.equals(new String(Base64.encode(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;
    }
}
TOP

Related Classes of org.apache.geronimo.security.realm.providers.SQLLoginModule

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.