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
018 package org.apache.geronimo.security;
019
020 import java.io.Serializable;
021 import java.security.AccessControlContext;
022 import java.security.AccessControlException;
023 import java.security.AccessController;
024 import java.security.InvalidKeyException;
025 import java.security.NoSuchAlgorithmException;
026 import java.security.Principal;
027 import java.security.PrivilegedAction;
028 import java.util.Collections;
029 import java.util.HashMap;
030 import java.util.IdentityHashMap;
031 import java.util.Map;
032 import java.util.Set;
033
034 import javax.crypto.Mac;
035 import javax.crypto.SecretKey;
036 import javax.crypto.spec.SecretKeySpec;
037 import javax.security.auth.Subject;
038 import javax.security.auth.callback.CallbackHandler;
039 import javax.security.auth.login.LoginContext;
040 import javax.security.auth.login.LoginException;
041 import javax.security.jacc.EJBRoleRefPermission;
042
043 import org.apache.geronimo.security.realm.providers.GeronimoCallerPrincipal;
044
045
046 /**
047 * @version $Rev: 565912 $ $Date: 2007-08-14 17:03:11 -0400 (Tue, 14 Aug 2007) $
048 */
049 public class ContextManager {
050
051 private static ThreadLocal<Serializable> currentCallerId = new ThreadLocal<Serializable>();
052 private static final ThreadLocal<Callers> callers = new ThreadLocal<Callers>();
053 private static Map<Subject, Context> subjectContexts = new IdentityHashMap<Subject, Context>();
054 private static Map<SubjectId, Subject> subjectIds = Collections.synchronizedMap(new HashMap<SubjectId, Subject>());
055 private static long nextSubjectId = System.currentTimeMillis();
056
057 private static SecretKey key;
058 private static String algorithm;
059 private static String password;
060
061 public static final GeronimoSecurityPermission GET_CONTEXT = new GeronimoSecurityPermission("getContext");
062 public static final GeronimoSecurityPermission SET_CONTEXT = new GeronimoSecurityPermission("setContext");
063
064 static {
065 password = "secret";
066 ContextManager.setAlgorithm("HmacSHA1");
067 }
068 public final static Subject EMPTY = new Subject();
069 static {
070 EMPTY.setReadOnly();
071 registerSubject(EMPTY);
072 }
073
074 public static LoginContext login(String realm, CallbackHandler callbackHandler) throws LoginException {
075 Subject subject = new Subject();
076 LoginContext loginContext = new LoginContext(realm, subject, callbackHandler);
077 loginContext.login();
078 SubjectId id = ContextManager.registerSubject(subject);
079 IdentificationPrincipal principal = new IdentificationPrincipal(id);
080 subject.getPrincipals().add(principal);
081 return loginContext;
082 }
083
084 public static void logout(LoginContext loginContext) throws LoginException {
085 Subject subject = loginContext.getSubject();
086 ContextManager.unregisterSubject(subject);
087 loginContext.logout();
088 }
089
090
091 /**
092 * After a login, the client is left with a relatively empty Subject, while
093 * the Subject used by the server has more important contents. This method
094 * lets a server-side component acting as an authentication client (such
095 * as Tocmat/Jetty) access the fully populated server-side Subject.
096 * @param clientSideSubject client simplification of actual subject
097 * @return full server side subject
098 */
099 public static Subject getServerSideSubject(Subject clientSideSubject) {
100 Set<IdentificationPrincipal> set = clientSideSubject.getPrincipals(IdentificationPrincipal.class);
101 if(set == null || set.size() == 0) {
102 return null;
103 }
104 IdentificationPrincipal idp = set.iterator().next();
105 return getRegisteredSubject(idp.getId());
106 }
107
108 public static void setCurrentCallerId(Serializable id) {
109 SecurityManager sm = System.getSecurityManager();
110 if (sm != null) sm.checkPermission(SET_CONTEXT);
111
112 currentCallerId.set(id);
113 }
114
115 public static Serializable getCurrentCallerId() {
116 SecurityManager sm = System.getSecurityManager();
117 if (sm != null) sm.checkPermission(GET_CONTEXT);
118
119 return currentCallerId.get();
120 }
121
122 public static void setCallers(Subject currentCaller, Subject nextCaller) {
123 SecurityManager sm = System.getSecurityManager();
124 if (sm != null) sm.checkPermission(SET_CONTEXT);
125 assert currentCaller != null;
126 assert nextCaller != null;
127 Callers newCallers = new Callers(currentCaller, nextCaller);
128 callers.set(newCallers);
129 }
130
131 public static void clearCallers() {
132 callers.set(null);
133 }
134
135 public static Callers getCallers() {
136 SecurityManager sm = System.getSecurityManager();
137 if (sm != null) sm.checkPermission(GET_CONTEXT);
138 return callers.get();
139 }
140
141 public static Callers setNextCaller(Subject nextCaller) {
142 SecurityManager sm = System.getSecurityManager();
143 if (sm != null) sm.checkPermission(SET_CONTEXT);
144 assert nextCaller != null;
145 Callers oldCallers = callers.get();
146 assert oldCallers != null;
147 Callers newCallers = new Callers(oldCallers.getNextCaller(), nextCaller);
148 callers.set(newCallers);
149 return oldCallers;
150 }
151
152 public static Callers pushNextCaller(Subject nextCaller) {
153 SecurityManager sm = System.getSecurityManager();
154 if (sm != null) sm.checkPermission(SET_CONTEXT);
155 Callers oldCallers = callers.get();
156 Subject oldNextCaller = oldCallers == null? null: oldCallers.getNextCaller();
157 Subject newNextCaller = (nextCaller == null || nextCaller == EMPTY)? oldNextCaller : nextCaller;
158 Callers newCallers = new Callers(oldNextCaller, newNextCaller);
159 callers.set(newCallers);
160 return oldCallers;
161 }
162
163 public static void popCallers(Callers oldCallers) {
164 SecurityManager sm = System.getSecurityManager();
165 if (sm != null) sm.checkPermission(SET_CONTEXT);
166 callers.set(oldCallers);
167 }
168
169 public static Subject getCurrentCaller() {
170 SecurityManager sm = System.getSecurityManager();
171 if (sm != null) sm.checkPermission(GET_CONTEXT);
172
173 Callers callers = ContextManager.callers.get();
174 return callers == null? null: callers.getCurrentCaller();
175 }
176
177 public static Subject getNextCaller() {
178 SecurityManager sm = System.getSecurityManager();
179 if (sm != null) sm.checkPermission(GET_CONTEXT);
180
181 Callers callers = ContextManager.callers.get();
182 return callers == null? null: callers.getNextCaller();
183 }
184
185 public static AccessControlContext getCurrentContext() {
186 SecurityManager sm = System.getSecurityManager();
187 if (sm != null) sm.checkPermission(GET_CONTEXT);
188
189 Callers threadLocalCallers = callers.get();
190 assert threadLocalCallers != null : "No current callers";
191 Subject currentSubject = threadLocalCallers.getCurrentCaller();
192 assert currentSubject != null : "No current caller";
193 Context context = subjectContexts.get(currentSubject);
194
195 assert context != null : "No registered context";
196
197 return context.context;
198 }
199
200 public static Principal getCurrentPrincipal(Subject callerSubject) {
201 SecurityManager sm = System.getSecurityManager();
202 if (sm != null) sm.checkPermission(GET_CONTEXT);
203
204 if (callerSubject == null) {
205 return new Principal() {
206 public String getName() {
207 return "";
208 }
209 };
210 }
211 Context context = subjectContexts.get(callerSubject);
212
213 assert context != null : "No registered context";
214
215 return context.principal;
216 }
217
218 public static SubjectId getCurrentId() {
219 SecurityManager sm = System.getSecurityManager();
220 if (sm != null) sm.checkPermission(GET_CONTEXT);
221
222 Callers threadLocalCallers = callers.get();
223 assert threadLocalCallers != null : "No current callers";
224 Subject currentSubject = threadLocalCallers.getCurrentCaller();
225 assert currentSubject != null : "No current caller";
226 Context context = subjectContexts.get(currentSubject);
227
228 assert context != null : "No registered context";
229
230 return context.id;
231 }
232
233 public static SubjectId getSubjectId(Subject subject) {
234 SecurityManager sm = System.getSecurityManager();
235 if (sm != null) sm.checkPermission(GET_CONTEXT);
236
237 Context context = subjectContexts.get(subject);
238
239 return (context != null ? context.id : null);
240 }
241
242 public static boolean isCallerInRole(String EJBName, String role) {
243 if (EJBName == null) throw new IllegalArgumentException("EJBName must not be null");
244 if (role == null) throw new IllegalArgumentException("Role must not be null");
245
246 try {
247 Callers currentCallers = callers.get();
248 if (currentCallers == null) {
249 return false;
250 }
251 Subject currentSubject = currentCallers.getCurrentCaller();
252 if (currentSubject == null) {
253 return false;
254 }
255
256 Context context = subjectContexts.get(currentSubject);
257
258 assert context != null : "No registered context";
259
260 context.context.checkPermission(new EJBRoleRefPermission(EJBName, role));
261 } catch (AccessControlException e) {
262 return false;
263 }
264 return true;
265 }
266
267 public static Subject getRegisteredSubject(SubjectId id) {
268 return subjectIds.get(id);
269 }
270
271 public static synchronized SubjectId registerSubject(Subject subject) {
272 SecurityManager sm = System.getSecurityManager();
273 if (sm != null) sm.checkPermission(SET_CONTEXT);
274
275 if (subject == null) throw new IllegalArgumentException("Subject must not be null");
276
277 AccessControlContext acc = (AccessControlContext) Subject.doAsPrivileged(subject, new PrivilegedAction() {
278 public Object run() {
279 return AccessController.getContext();
280 }
281 }, null);
282
283 Context context = new Context();
284 context.subject = subject;
285 context.context = acc;
286 Set<? extends Principal> principals = subject.getPrincipals(GeronimoCallerPrincipal.class);
287 if (!principals.isEmpty()) {
288 context.principal = principals.iterator().next();
289 } else if (!(principals = subject.getPrincipals(PrimaryRealmPrincipal.class)).isEmpty()) {
290 context.principal = principals.iterator().next();
291 } else if (!(principals = subject.getPrincipals(RealmPrincipal.class)).isEmpty()) {
292 context.principal = principals.iterator().next();
293 } else if (!(principals = subject.getPrincipals()).isEmpty()) {
294 context.principal = principals.iterator().next();
295 }
296 Long id = nextSubjectId++;
297 context.id = new SubjectId(id, hash(id));
298
299 subjectIds.put(context.id, subject);
300 subjectContexts.put(subject, context);
301
302 return context.id;
303 }
304
305 public static synchronized void unregisterSubject(Subject subject) {
306 SecurityManager sm = System.getSecurityManager();
307 if (sm != null) sm.checkPermission(SET_CONTEXT);
308
309 if (subject == null) throw new IllegalArgumentException("Subject must not be null");
310
311 Context context = subjectContexts.get(subject);
312 if (context == null) return;
313
314 subjectIds.remove(context.id);
315 subjectContexts.remove(subject);
316 }
317
318 /**
319 * Obtain the thread's identifying principal.
320 * <p/>
321 * Clients should use <code>Subject.doAs*</code> to associate a Subject
322 * with the thread's call stack. It is this Subject that will be used for
323 * authentication checks.
324 * <p/>
325 * Return a <code>IdentificationPrincipal</code>. This kind of principal
326 * is inserted into a subject if one uses one of the Geronimo LoginModules.
327 * It is a secure id that identifies the Subject.
328 *
329 * @return the principal that identifies the Subject of this thread.
330 * @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedAction)
331 * @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedExceptionAction)
332 * @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedAction, java.security.AccessControlContext)
333 * @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)
334 */
335 public static IdentificationPrincipal getThreadPrincipal() {
336 SecurityManager sm = System.getSecurityManager();
337 if (sm != null) sm.checkPermission(GET_CONTEXT);
338
339 Subject subject = Subject.getSubject(AccessController.getContext());
340 if (subject != null) {
341 Set set = subject.getPrincipals(IdentificationPrincipal.class);
342 if (!set.isEmpty()) return (IdentificationPrincipal) set.iterator().next();
343 }
344 return null;
345 }
346
347 public static String getAlgorithm() {
348 SecurityManager sm = System.getSecurityManager();
349 if (sm != null) sm.checkPermission(GET_CONTEXT);
350
351 return algorithm;
352 }
353
354 public static void setAlgorithm(String algorithm) {
355 SecurityManager sm = System.getSecurityManager();
356 if (sm != null) sm.checkPermission(SET_CONTEXT);
357
358 ContextManager.algorithm = algorithm;
359
360 key = new SecretKeySpec(password.getBytes(), algorithm);
361
362 /**
363 * Make sure that we can generate the Mac.
364 */
365 try {
366 Mac mac = Mac.getInstance(algorithm);
367 mac.init(key);
368 } catch (NoSuchAlgorithmException e) {
369 assert false : "Should never have reached here";
370 } catch (InvalidKeyException e) {
371 assert false : "Should never have reached here";
372 }
373 }
374
375 public static String getPassword() {
376 SecurityManager sm = System.getSecurityManager();
377 if (sm != null) sm.checkPermission(GET_CONTEXT);
378
379 return password;
380 }
381
382 public static void setPassword(String password) {
383 SecurityManager sm = System.getSecurityManager();
384 if (sm != null) sm.checkPermission(SET_CONTEXT);
385
386 ContextManager.password = password;
387
388 key = new SecretKeySpec(password.getBytes(), algorithm);
389 }
390
391 private static byte[] hash(Long id) {
392 long n = id;
393 byte[] bytes = new byte[8];
394 for (int i = 7; i >= 0; i--) {
395 bytes[i] = (byte) (n);
396 n >>>= 8;
397 }
398
399 try {
400 Mac mac = Mac.getInstance(algorithm);
401 mac.init(key);
402 mac.update(bytes);
403
404 return mac.doFinal();
405 } catch (NoSuchAlgorithmException e) {
406 //shouldn't happen
407 } catch (InvalidKeyException e) {
408 //shouldn't happen
409 }
410 assert false : "Should never have reached here";
411 return null;
412 }
413
414 private static class Context {
415 SubjectId id;
416 AccessControlContext context;
417 Subject subject;
418 Principal principal;
419 }
420
421 }