001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.activemq.jaas;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.security.Principal;
022    import java.util.Enumeration;
023    import java.util.HashSet;
024    import java.util.Map;
025    import java.util.Properties;
026    import java.util.Set;
027    
028    import javax.security.auth.Subject;
029    import javax.security.auth.callback.Callback;
030    import javax.security.auth.callback.CallbackHandler;
031    import javax.security.auth.callback.NameCallback;
032    import javax.security.auth.callback.PasswordCallback;
033    import javax.security.auth.callback.UnsupportedCallbackException;
034    import javax.security.auth.login.FailedLoginException;
035    import javax.security.auth.login.LoginException;
036    import javax.security.auth.spi.LoginModule;
037    
038    import org.slf4j.Logger;
039    import org.slf4j.LoggerFactory;
040    
041    public class PropertiesLoginModule implements LoginModule {
042    
043        private static final String USER_FILE = "org.apache.activemq.jaas.properties.user";
044        private static final String GROUP_FILE = "org.apache.activemq.jaas.properties.group";
045    
046        private static final Logger LOG = LoggerFactory.getLogger(PropertiesLoginModule.class);
047    
048        private Subject subject;
049        private CallbackHandler callbackHandler;
050    
051        private boolean debug;
052        private boolean reload = false;
053        private static String usersFile;
054        private static String groupsFile;
055        private static Properties users;
056        private static Properties groups;
057        private static long usersReloadTime = 0;
058        private static long groupsReloadTime = 0;
059        private String user;
060        private Set<Principal> principals = new HashSet<Principal>();
061        private File baseDir;
062        private boolean loginSucceeded;
063    
064        @Override
065        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
066            this.subject = subject;
067            this.callbackHandler = callbackHandler;
068            loginSucceeded = false;
069    
070            debug = "true".equalsIgnoreCase((String)options.get("debug"));
071            if (options.get("reload") != null) {
072                reload = "true".equalsIgnoreCase((String)options.get("reload"));
073            }
074    
075            if (options.get("baseDir") != null) {
076                baseDir = new File((String)options.get("baseDir"));
077            }
078    
079            setBaseDir();
080            usersFile = (String) options.get(USER_FILE) + "";
081            File uf = baseDir != null ? new File(baseDir, usersFile) : new File(usersFile);
082    
083            if (reload || users == null || uf.lastModified() > usersReloadTime) {
084                if (debug) {
085                    LOG.debug("Reloading users from " + uf.getAbsolutePath());
086                }
087                try {
088                    users = new Properties();
089                    java.io.FileInputStream in = new java.io.FileInputStream(uf);
090                    users.load(in);
091                    in.close();
092                    usersReloadTime = System.currentTimeMillis();
093                } catch (IOException ioe) {
094                    LOG.warn("Unable to load user properties file " + uf);
095                }
096            }
097    
098            groupsFile = (String) options.get(GROUP_FILE) + "";
099            File gf = baseDir != null ? new File(baseDir, groupsFile) : new File(groupsFile);
100            if (reload || groups == null || gf.lastModified() > groupsReloadTime) {
101                if (debug) {
102                    LOG.debug("Reloading groups from " + gf.getAbsolutePath());
103                }
104                try {
105                    groups = new Properties();
106                    java.io.FileInputStream in = new java.io.FileInputStream(gf);
107                    groups.load(in);
108                    in.close();
109                    groupsReloadTime = System.currentTimeMillis();
110                } catch (IOException ioe) {
111                    LOG.warn("Unable to load group properties file " + gf);
112                }
113            }
114        }
115    
116        private void setBaseDir() {
117            if (baseDir == null) {
118                if (System.getProperty("java.security.auth.login.config") != null) {
119                    baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
120                    if (debug) {
121                        LOG.debug("Using basedir=" + baseDir.getAbsolutePath());
122                    }
123                }
124            }
125        }
126    
127        @Override
128        public boolean login() throws LoginException {
129            Callback[] callbacks = new Callback[2];
130    
131            callbacks[0] = new NameCallback("Username: ");
132            callbacks[1] = new PasswordCallback("Password: ", false);
133            try {
134                callbackHandler.handle(callbacks);
135            } catch (IOException ioe) {
136                throw new LoginException(ioe.getMessage());
137            } catch (UnsupportedCallbackException uce) {
138                throw new LoginException(uce.getMessage() + " not available to obtain information from user");
139            }
140            user = ((NameCallback)callbacks[0]).getName();
141            char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
142            if (tmpPassword == null) {
143                tmpPassword = new char[0];
144            }
145            if (user == null) {
146                throw new FailedLoginException("user name is null");
147            }
148            String password = users.getProperty(user);
149    
150            if (password == null) {
151                throw new FailedLoginException("User does exist");
152            }
153            if (!password.equals(new String(tmpPassword))) {
154                throw new FailedLoginException("Password does not match");
155            }
156            loginSucceeded = true;
157    
158            if (debug) {
159                LOG.debug("login " + user);
160            }
161            return loginSucceeded;
162        }
163    
164        @Override
165        public boolean commit() throws LoginException {
166            boolean result = loginSucceeded;
167            if (result) {
168                principals.add(new UserPrincipal(user));
169    
170                for (Enumeration<?> enumeration = groups.keys(); enumeration.hasMoreElements();) {
171                    String name = (String)enumeration.nextElement();
172                    String[] userList = ((String)groups.getProperty(name) + "").split(",");
173                    for (int i = 0; i < userList.length; i++) {
174                        if (user.equals(userList[i])) {
175                            principals.add(new GroupPrincipal(name));
176                            break;
177                        }
178                    }
179                }
180    
181                subject.getPrincipals().addAll(principals);
182            }
183    
184            // will whack loginSucceeded
185            clear();
186    
187            if (debug) {
188                LOG.debug("commit, result: " + result);
189            }
190            return result;
191        }
192    
193        @Override
194        public boolean abort() throws LoginException {
195            clear();
196    
197            if (debug) {
198                LOG.debug("abort");
199            }
200            return true;
201        }
202    
203        @Override
204        public boolean logout() throws LoginException {
205            subject.getPrincipals().removeAll(principals);
206            principals.clear();
207            clear();
208            if (debug) {
209                LOG.debug("logout");
210            }
211            return true;
212        }
213    
214        private void clear() {
215            user = null;
216            loginSucceeded = false;
217        }
218    }