001    /**
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  You may obtain a copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.activemq.jaas;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.security.cert.X509Certificate;
024    import java.util.Enumeration;
025    import java.util.HashSet;
026    import java.util.Map;
027    import java.util.Properties;
028    import java.util.Set;
029    
030    import javax.security.auth.Subject;
031    import javax.security.auth.callback.CallbackHandler;
032    import javax.security.auth.login.LoginException;
033    
034    /**
035     * A LoginModule allowing for SSL certificate based authentication based on Distinguished Names (DN) stored in text
036     *      files.
037     *      
038     * The DNs are parsed using a Properties class where each line is <user_name>=<user_DN>.
039     * This class also uses a group definition file where each line is <group_name>=<user_name_1>,<user_name_2>,etc.
040     * The user and group files' locations must be specified in the org.apache.activemq.jaas.textfiledn.user and
041     *      org.apache.activemq.jaas.textfiledn.user properties respectively.
042     * 
043     * NOTE: This class will re-read user and group files for every authentication (i.e it does live updates of allowed
044     *      groups and users).
045     * 
046     * @author sepandm@gmail.com (Sepand)
047     */
048    public class TextFileCertificateLoginModule extends CertificateLoginModule {
049        
050        private final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
051        private final String GROUP_FILE = "org.apache.activemq.jaas.textfiledn.group";
052        
053        private File baseDir;
054        private String usersFilePathname;
055        private String groupsFilePathname;
056        
057        /**
058         * Performs initialization of file paths.
059         * 
060         * A standard JAAS override.
061         */
062        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
063            super.initialize(subject, callbackHandler, sharedState, options);       
064            if (System.getProperty("java.security.auth.login.config") != null) {
065                baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
066            } else {
067                baseDir = new File(".");
068            }
069            
070            usersFilePathname = (String) options.get(USER_FILE)+"";
071            groupsFilePathname = (String) options.get(GROUP_FILE)+"";
072        }
073        
074        /**
075         * Overriding to allow DN authorization based on DNs specified in text files.
076         *  
077         * @param certs The certificate the incoming connection provided.
078         * @return The user's authenticated name or null if unable to authenticate the user.
079         * @throws LoginException Thrown if unable to find user file or connection certificate. 
080         */
081        protected String getUserNameForCertificates(final X509Certificate[] certs) throws LoginException {
082            if (certs == null) {
083                throw new LoginException("Client certificates not found. Cannot authenticate.");
084            }
085            
086            File usersFile = new File(baseDir,usersFilePathname);
087            
088            Properties users = new Properties();
089            
090            try {
091                users.load(new java.io.FileInputStream(usersFile));
092            } catch (IOException ioe) {
093                throw new LoginException("Unable to load user properties file " + usersFile);
094            }
095            
096            String dn = getDistinguishedName(certs);
097            
098            for(Enumeration vals = users.elements(), keys = users.keys(); vals.hasMoreElements(); ) {
099                if ( ((String)vals.nextElement()).equals(dn) ) {
100                    return (String)keys.nextElement();
101                } else {
102                    keys.nextElement();
103                }
104            }
105            
106            return null;
107        }
108        
109        /**
110         * Overriding to allow for group discovery based on text files.
111         * 
112         * @param username The name of the user being examined. This is the same name returned by
113         *      getUserNameForCertificates.
114         * @return A Set of name Strings for groups this user belongs to.
115         * @throws LoginException Thrown if unable to find group definition file.
116         */
117        protected Set getUserGroups(String username) throws LoginException {
118            File groupsFile = new File(baseDir, groupsFilePathname);
119            
120            Properties groups = new Properties();
121            try {
122                groups.load(new java.io.FileInputStream(groupsFile));
123            } catch (IOException ioe) {
124                throw new LoginException("Unable to load group properties file " + groupsFile);
125            }
126            Set userGroups = new HashSet();
127            for (Enumeration enumeration = groups.keys(); enumeration.hasMoreElements();) {
128                String groupName = (String) enumeration.nextElement();
129                String[] userList = (groups.getProperty(groupName) + "").split(",");
130                for (int i = 0; i < userList.length; i++) {
131                    if (username.equals(userList[i])) {
132                        userGroups.add(groupName);
133                        break;
134                    }
135                }
136            }
137            
138            return userGroups;
139        }
140    }