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.HashSet;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import javax.security.auth.Subject;
027    import javax.security.auth.callback.Callback;
028    import javax.security.auth.callback.CallbackHandler;
029    import javax.security.auth.callback.NameCallback;
030    import javax.security.auth.callback.PasswordCallback;
031    import javax.security.auth.callback.UnsupportedCallbackException;
032    import javax.security.auth.login.FailedLoginException;
033    import javax.security.auth.login.LoginException;
034    import javax.security.auth.spi.LoginModule;
035    
036    import org.slf4j.Logger;
037    import org.slf4j.LoggerFactory;
038    
039    public class PropertiesLoginModule implements LoginModule {
040    
041        private static final String USER_FILE = "org.apache.activemq.jaas.properties.user";
042        private static final String GROUP_FILE = "org.apache.activemq.jaas.properties.group";
043    
044        private static final Logger LOG = LoggerFactory.getLogger(PropertiesLoginModule.class);
045    
046        private Subject subject;
047        private CallbackHandler callbackHandler;
048    
049        private boolean debug;
050        private boolean reload = false;
051        private static volatile PrincipalProperties users;
052        private static volatile PrincipalProperties groups;
053        private String user;
054        private final Set<Principal> principals = new HashSet<Principal>();
055        private File baseDir;
056        private boolean loginSucceeded;
057    
058        @Override
059        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
060            this.subject = subject;
061            this.callbackHandler = callbackHandler;
062            loginSucceeded = false;
063    
064            debug = "true".equalsIgnoreCase((String) options.get("debug"));
065            if (options.get("reload") != null) {
066                reload = "true".equalsIgnoreCase((String) options.get("reload"));
067            }
068    
069            if (options.get("baseDir") != null) {
070                baseDir = new File((String) options.get("baseDir"));
071            }
072    
073            setBaseDir();
074            String usersFile = (String) options.get(USER_FILE) + "";
075            File uf = baseDir != null ? new File(baseDir, usersFile) : new File(usersFile);
076    
077            if (reload || users == null || uf.lastModified() > users.getReloadTime()) {
078                if (debug) {
079                    LOG.debug("Reloading users from " + uf.getAbsolutePath());
080                }
081                users = new PrincipalProperties("user", uf, LOG);
082            }
083    
084            String groupsFile = (String) options.get(GROUP_FILE) + "";
085            File gf = baseDir != null ? new File(baseDir, groupsFile) : new File(groupsFile);
086            if (reload || groups == null || gf.lastModified() > groups.getReloadTime()) {
087                if (debug) {
088                    LOG.debug("Reloading groups from " + gf.getAbsolutePath());
089                }
090                groups = new PrincipalProperties("group", gf, LOG);
091            }
092        }
093    
094        private void setBaseDir() {
095            if (baseDir == null) {
096                if (System.getProperty("java.security.auth.login.config") != null) {
097                    baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
098                    if (debug) {
099                        LOG.debug("Using basedir=" + baseDir.getAbsolutePath());
100                    }
101                }
102            }
103        }
104    
105        @Override
106        public boolean login() throws LoginException {
107            Callback[] callbacks = new Callback[2];
108    
109            callbacks[0] = new NameCallback("Username: ");
110            callbacks[1] = new PasswordCallback("Password: ", false);
111            try {
112                callbackHandler.handle(callbacks);
113            } catch (IOException ioe) {
114                throw new LoginException(ioe.getMessage());
115            } catch (UnsupportedCallbackException uce) {
116                throw new LoginException(uce.getMessage() + " not available to obtain information from user");
117            }
118            user = ((NameCallback) callbacks[0]).getName();
119            char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
120            if (tmpPassword == null) {
121                tmpPassword = new char[0];
122            }
123            if (user == null) {
124                throw new FailedLoginException("user name is null");
125            }
126            String password = users.getProperty(user);
127    
128            if (password == null) {
129                throw new FailedLoginException("User does exist");
130            }
131            if (!password.equals(new String(tmpPassword))) {
132                throw new FailedLoginException("Password does not match");
133            }
134            loginSucceeded = true;
135    
136            if (debug) {
137                LOG.debug("login " + user);
138            }
139            return loginSucceeded;
140        }
141    
142        @Override
143        public boolean commit() throws LoginException {
144            boolean result = loginSucceeded;
145            if (result) {
146                principals.add(new UserPrincipal(user));
147    
148                for (Map.Entry<String, String> entry : groups.entries()) {
149                    String name = entry.getKey();
150                    String[] userList = entry.getValue().split(",");
151                    for (int i = 0; i < userList.length; i++) {
152                        if (user.equals(userList[i])) {
153                            principals.add(new GroupPrincipal(name));
154                            break;
155                        }
156                    }
157                }
158    
159                subject.getPrincipals().addAll(principals);
160            }
161    
162            // will whack loginSucceeded
163            clear();
164    
165            if (debug) {
166                LOG.debug("commit, result: " + result);
167            }
168            return result;
169        }
170    
171        @Override
172        public boolean abort() throws LoginException {
173            clear();
174    
175            if (debug) {
176                LOG.debug("abort");
177            }
178            return true;
179        }
180    
181        @Override
182        public boolean logout() throws LoginException {
183            subject.getPrincipals().removeAll(principals);
184            principals.clear();
185            clear();
186            if (debug) {
187                LOG.debug("logout");
188            }
189            return true;
190        }
191    
192        private void clear() {
193            user = null;
194            loginSucceeded = false;
195        }
196    
197        /**
198         * For test-usage only.
199         */
200        static void resetUsersAndGroupsCache() {
201            users = null;
202            groups = null;
203        }
204    }