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 }