1   /**
2    *
3    * Copyright 2003-2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.geronimo.security.jaas.client;
18  
19  import java.util.HashMap;
20  import java.util.Map;
21  import java.util.Set;
22  import javax.management.MalformedObjectNameException;
23  import javax.management.ObjectName;
24  import javax.security.auth.Subject;
25  import javax.security.auth.callback.CallbackHandler;
26  import javax.security.auth.login.FailedLoginException;
27  import javax.security.auth.login.LoginException;
28  import javax.security.auth.spi.LoginModule;
29  
30  import org.apache.geronimo.kernel.Kernel;
31  import org.apache.geronimo.kernel.KernelRegistry;
32  import org.apache.geronimo.kernel.GBeanNotFoundException;
33  import org.apache.geronimo.security.jaas.server.JaasSessionId;
34  import org.apache.geronimo.security.jaas.server.JaasLoginModuleConfiguration;
35  import org.apache.geronimo.security.jaas.LoginModuleControlFlag;
36  import org.apache.geronimo.security.jaas.LoginUtils;
37  import org.apache.geronimo.security.jaas.server.JaasLoginServiceMBean;
38  import org.apache.geronimo.security.remoting.jmx.JaasLoginServiceRemotingClient;
39  
40  
41  /**
42   * A LoginModule implementation which connects to a Geronimo server under
43   * the covers, and uses Geronimo realms to resolve the login.  It handles a
44   * mix of client-side and server-side login modules.  It treats any client
45   * side module as something it should manage and execute, while a server side
46   * login module would be managed and executed by the Geronimo server.
47   * <p/>
48   * Note that this can actually be run from within a Geronimo server, in which
49   * case the client/server distinction is somewhat less important, and the
50   * communication is optimized by avoiding network traffic.
51   *
52   * @version $Rev: 472291 $ $Date: 2006-11-07 13:51:35 -0800 (Tue, 07 Nov 2006) $
53   */
54  public class JaasLoginCoordinator implements LoginModule {
55      public final static String OPTION_HOST = "host";
56      public final static String OPTION_PORT = "port";
57      public final static String OPTION_KERNEL = "kernel";
58      public final static String OPTION_REALM = "realm";
59      public final static String OPTION_SERVICENAME = "serviceName";
60      public final static String OPTION_SERVICE_INSTANCE = "serviceInstance";
61      private String serverHost;
62      private int serverPort;
63      private String realmName;
64      private String kernelName;
65      private ObjectName serviceName;
66      private JaasLoginServiceMBean service;
67      private CallbackHandler handler;
68      private Subject subject;
69      private JaasSessionId sessionHandle;
70      private LoginModuleProxy[] proxies;
71      private final Map sharedState = new HashMap();
72  
73  
74      public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
75          serverHost = (String) options.get(OPTION_HOST);
76          Object port = options.get(OPTION_PORT);
77          if (port != null) {
78              serverPort = Integer.parseInt((String) port);
79          }
80          realmName = (String) options.get(OPTION_REALM);
81          kernelName = (String) options.get(OPTION_KERNEL);
82          try {
83              String s = (String) options.get(OPTION_SERVICENAME);
84              serviceName = s != null ? new ObjectName(s) : null;
85          } catch (MalformedObjectNameException e) {
86              throw new IllegalArgumentException("option " + OPTION_SERVICENAME + "is not a valid ObjectName: " + options.get(OPTION_SERVICENAME));
87          }
88          if (port != null || kernelName != null) {
89              service = connect();
90          } else {
91              
92              service = (JaasLoginServiceMBean) options.get(OPTION_SERVICE_INSTANCE);
93          }
94          handler = callbackHandler;
95          if (subject == null) {
96              this.subject = new Subject();
97          } else {
98              this.subject = subject;
99          }
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             
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;  
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         
235         if (success != null) {
236             return success.booleanValue();
237         }
238         
239         if (backup != null) {
240             return backup.booleanValue();
241         }
242         
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 }