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.geronimo.security.jaas.client;
018    
019    import java.util.HashMap;
020    import java.util.Map;
021    import java.util.Set;
022    import javax.management.MalformedObjectNameException;
023    import javax.management.ObjectName;
024    import javax.security.auth.Subject;
025    import javax.security.auth.callback.CallbackHandler;
026    import javax.security.auth.login.FailedLoginException;
027    import javax.security.auth.login.LoginException;
028    import javax.security.auth.spi.LoginModule;
029    
030    import org.apache.geronimo.kernel.Kernel;
031    import org.apache.geronimo.kernel.KernelRegistry;
032    import org.apache.geronimo.kernel.GBeanNotFoundException;
033    import org.apache.geronimo.security.jaas.server.JaasSessionId;
034    import org.apache.geronimo.security.jaas.server.JaasLoginModuleConfiguration;
035    import org.apache.geronimo.security.jaas.LoginModuleControlFlag;
036    import org.apache.geronimo.security.jaas.LoginUtils;
037    import org.apache.geronimo.security.jaas.server.JaasLoginServiceMBean;
038    import org.apache.geronimo.security.remoting.jmx.JaasLoginServiceRemotingClient;
039    
040    
041    /**
042     * A LoginModule implementation which connects to a Geronimo server under
043     * the covers, and uses Geronimo realms to resolve the login.  It handles a
044     * mix of client-side and server-side login modules.  It treats any client
045     * side module as something it should manage and execute, while a server side
046     * login module would be managed and executed by the Geronimo server.
047     * <p/>
048     * Note that this can actually be run from within a Geronimo server, in which
049     * case the client/server distinction is somewhat less important, and the
050     * communication is optimized by avoiding network traffic.
051     *
052     * @version $Rev: 487175 $ $Date: 2006-12-14 03:10:31 -0800 (Thu, 14 Dec 2006) $
053     */
054    public class JaasLoginCoordinator implements LoginModule {
055        public final static String OPTION_HOST = "host";
056        public final static String OPTION_PORT = "port";
057        public final static String OPTION_KERNEL = "kernel";
058        public final static String OPTION_REALM = "realm";
059        public final static String OPTION_SERVICENAME = "serviceName";
060        public final static String OPTION_SERVICE_INSTANCE = "serviceInstance";
061        private String serverHost;
062        private int serverPort;
063        private String realmName;
064        private String kernelName;
065        private ObjectName serviceName;
066        private JaasLoginServiceMBean service;
067        private CallbackHandler handler;
068        private Subject subject;
069        private JaasSessionId sessionHandle;
070        private LoginModuleProxy[] proxies;
071        private final Map sharedState = new HashMap();
072    
073    
074        public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
075            serverHost = (String) options.get(OPTION_HOST);
076            Object port = options.get(OPTION_PORT);
077            if (port != null) {
078                serverPort = Integer.parseInt((String) port);
079            }
080            realmName = (String) options.get(OPTION_REALM);
081            kernelName = (String) options.get(OPTION_KERNEL);
082            try {
083                String s = (String) options.get(OPTION_SERVICENAME);
084                serviceName = s != null ? new ObjectName(s) : null;
085            } catch (MalformedObjectNameException e) {
086                throw new IllegalArgumentException("option " + OPTION_SERVICENAME + "is not a valid ObjectName: " + options.get(OPTION_SERVICENAME));
087            }
088            if (port != null || kernelName != null) {
089                service = connect();
090            } else {
091                //primarily for testing without a kernel
092                service = (JaasLoginServiceMBean) options.get(OPTION_SERVICE_INSTANCE);
093            }
094            handler = callbackHandler;
095            if (subject == null) {
096                this.subject = new Subject();
097            } else {
098                this.subject = subject;
099            }
100        }
101    
102        public boolean login() throws LoginException {
103            sessionHandle = service.connectToRealm(realmName);
104            JaasLoginModuleConfiguration[] config = service.getLoginConfiguration(sessionHandle);
105            proxies = new LoginModuleProxy[config.length];
106    
107            for (int i = 0; i < proxies.length; i++) {
108                if (config[i].isServerSide()) {
109                    proxies[i] = new ServerLoginProxy(config[i].getFlag(), subject, i, service, sessionHandle);
110                } else {
111                    LoginModule source = config[i].getLoginModule(JaasLoginCoordinator.class.getClassLoader());
112                    if (config[i].isWrapPrincipals()) {
113                        proxies[i] = new WrappingClientLoginModuleProxy(config[i].getFlag(), subject, source, config[i].getLoginDomainName(), realmName);
114                    } else {
115                        proxies[i] = new ClientLoginModuleProxy(config[i].getFlag(), subject, source);
116                    }
117                }
118                proxies[i].initialize(subject, handler, sharedState, config[i].getOptions());
119                syncSharedState();
120            }
121            boolean result = performLogin();
122            if(result) {
123                return true;
124            } else {
125                // login() method should throw LoginException incase of failure
126                throw new FailedLoginException();
127            }
128        }
129    
130        public boolean commit() throws LoginException {
131            for (int i = 0; i < proxies.length; i++) {
132                proxies[i].commit();
133                syncSharedState();
134                syncPrincipals();
135            }
136            subject.getPrincipals().add(service.loginSucceeded(sessionHandle));
137            return true;
138        }
139    
140        public boolean abort() throws LoginException {
141            try {
142                for (int i = 0; i < proxies.length; i++) {
143                    proxies[i].abort();
144                    syncSharedState();
145                }
146            } finally {
147                service.loginFailed(sessionHandle);
148            }
149            clear();
150            return true;
151        }
152    
153        public boolean logout() throws LoginException {
154            try {
155                for (int i = 0; i < proxies.length; i++) {
156                    proxies[i].logout();
157                    syncSharedState();
158                }
159            } finally {
160                service.logout(sessionHandle);
161            }
162            clear();
163            return true;
164        }
165    
166        private void clear() {
167            service = null;
168            serverHost = null;
169            serverPort = 0;
170            realmName = null;
171            kernelName = null;
172            service = null;
173            handler = null;
174            subject = null;
175            sessionHandle = null;
176            proxies = null;
177        }
178    
179        private JaasLoginServiceMBean connect() {
180            if (serverHost != null && serverPort > 0) {
181                return JaasLoginServiceRemotingClient.create(serverHost, serverPort);
182            } else {
183                Kernel kernel = KernelRegistry.getKernel(kernelName);
184                try {
185                    return (JaasLoginServiceMBean) kernel.getGBean(serviceName);
186                } catch (GBeanNotFoundException e) {
187                    IllegalStateException illegalStateException = new IllegalStateException();
188                    illegalStateException.initCause(e);
189                    throw illegalStateException;
190                }
191            }
192        }
193    
194        /**
195         * See http://java.sun.com/j2se/1.4.2/docs/api/javax/security/auth/login/Configuration.html
196         *
197         * @return
198         * @throws LoginException
199         */
200        private boolean performLogin() throws LoginException {
201            Boolean success = null;
202            Boolean backup = null;
203    
204            for (int i = 0; i < proxies.length; i++) {
205                LoginModuleProxy proxy = proxies[i];
206                boolean result;
207                try {
208                    result = proxy.login();
209                } catch(LoginException e) {
210                    result = false;  // login() method throws LoginException incase of failure
211                }
212                syncSharedState();
213    
214                if (proxy.getControlFlag() == LoginModuleControlFlag.REQUIRED) {
215                    if (success == null || success.booleanValue()) {
216                        success = result ? Boolean.TRUE : Boolean.FALSE;
217                    }
218                } else if (proxy.getControlFlag() == LoginModuleControlFlag.REQUISITE) {
219                    if (!result) {
220                        return false;
221                    } else if (success == null) {
222                        success = Boolean.TRUE;
223                    }
224                } else if (proxy.getControlFlag() == LoginModuleControlFlag.SUFFICIENT) {
225                    if (result && (success == null || success.booleanValue())) {
226                        return true;
227                    }
228                } else if (proxy.getControlFlag() == LoginModuleControlFlag.OPTIONAL) {
229                    if (backup == null || backup.booleanValue()) {
230                        backup = result ? Boolean.TRUE : Boolean.FALSE;
231                    }
232                }
233            }
234            // all required and requisite modules succeeded, or at least one required module failed
235            if (success != null) {
236                return success.booleanValue();
237            }
238            // no required or requisite modules, no sufficient modules succeeded, fall back to optional modules
239            if (backup != null) {
240                return backup.booleanValue();
241            }
242            // perhaps only a sufficient module, and it failed
243            return false;
244        }
245    
246        private void syncSharedState() throws LoginException {
247            Map map = service.syncShareState(sessionHandle, LoginUtils.getSerializableCopy(sharedState));
248            sharedState.putAll(map);
249        }
250    
251        private void syncPrincipals() throws LoginException {
252            Set principals = service.syncPrincipals(sessionHandle, subject.getPrincipals());
253            subject.getPrincipals().addAll(principals);
254        }
255    }