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 }