/**
 * 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.activemq.jaas;

import java.io.File;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;

/**
 * A LoginModule allowing for SSL certificate based authentication based on
 * Distinguished Names (DN) stored in text files. The DNs are parsed using a
 * Properties class where each line is <user_name>=<user_DN>. This class also
 * uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,etc.
 * The user and group files' locations must be specified in the
 * org.apache.activemq.jaas.textfiledn.user and
 * org.apache.activemq.jaas.textfiledn.user properties respectively. NOTE: This
 * class will re-read user and group files for every authentication (i.e it does
 * live updates of allowed groups and users).
 * 
 * @author sepandm@gmail.com (Sepand)
 */
public class TextFileCertificateLoginModule extends CertificateLoginModule {

    private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
    private static final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group";

    private File baseDir;
    private String usersFilePathname;
    private String groupsFilePathname;

    /**
     * Performs initialization of file paths. A standard JAAS override.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
        super.initialize(subject, callbackHandler, sharedState, options);
        if (System.getProperty("java.security.auth.login.config") != null) {
            baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
        } else {
            baseDir = new File(".");
        }

        usersFilePathname = (String)options.get(USER_FILE) + "";
        groupsFilePathname = (String)options.get(GROUP_FILE) + "";
    }

    /**
     * Overriding to allow DN authorization based on DNs specified in text
     * files.
     * 
     * @param certs The certificate the incoming connection provided.
     * @return The user's authenticated name or null if unable to authenticate
     *         the user.
     * @throws LoginException Thrown if unable to find user file or connection
     *                 certificate.
     */
    protected String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException {
        if (certs == null) {
            throw new LoginException("Client certificates not found. Cannot authenticate.");
        }

        File usersFile = new File(baseDir, usersFilePathname);

        Properties users = new Properties();

        try {
            java.io.FileInputStream in = new java.io.FileInputStream(usersFile);
            users.load(in);
            in.close();
        } catch (IOException ioe) {
            throw new LoginException("Unable to load user properties file " + usersFile);
        }

        String dn = getDistinguishedName(certs);

        Enumeration<Object> keys = users.keys();
        for (Enumeration vals = users.elements(); vals.hasMoreElements();) {
            if (((String)vals.nextElement()).equals(dn)) {
                return (String)keys.nextElement();
            } else {
                keys.nextElement();
            }
        }

        return null;
    }

    /**
     * Overriding to allow for group discovery based on text files.
     * 
     * @param username The name of the user being examined. This is the same
     *                name returned by getUserNameForCertificates.
     * @return A Set of name Strings for groups this user belongs to.
     * @throws LoginException Thrown if unable to find group definition file.
     */
    protected Set<String> getUserGroups(String username) throws LoginException {
        File groupsFile = new File(baseDir, groupsFilePathname);

        Properties groups = new Properties();
        try {
            java.io.FileInputStream in = new java.io.FileInputStream(groupsFile);
            groups.load(in);
            in.close();
        } catch (IOException ioe) {
            throw new LoginException("Unable to load group properties file " + groupsFile);
        }
        Set<String> userGroups = new HashSet<String>();
        for (Enumeration enumeration = groups.keys(); enumeration.hasMoreElements();) {
            String groupName = (String)enumeration.nextElement();
            String[] userList = (groups.getProperty(groupName) + "").split(",");
            for (int i = 0; i < userList.length; i++) {
                if (username.equals(userList[i])) {
                    userGroups.add(groupName);
                    break;
                }
            }
        }

        return userGroups;
    }
}
