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.security.AccessControlContext; 021 import java.security.AccessControlException; 022 import java.security.AccessController; 023 import java.security.InvalidKeyException; 024 import java.security.NoSuchAlgorithmException; 025 import java.security.Principal; 026 import java.security.PrivilegedAction; 027 import java.security.ProviderException; 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: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 048 */ 049 public class ContextManager { 050 051 private static final ThreadLocal<Callers> callers = new ThreadLocal<Callers>(); 052 private static Map<Subject, Context> subjectContexts = new IdentityHashMap<Subject, Context>(); 053 private static Map<SubjectId, Subject> subjectIds = Collections.synchronizedMap(new HashMap<SubjectId, Subject>()); 054 private static long nextSubjectId = System.currentTimeMillis(); 055 056 private static SecretKey key; 057 private static String algorithm; 058 private static String password; 059 060 public static final GeronimoSecurityPermission GET_CONTEXT = new GeronimoSecurityPermission("getContext"); 061 public static final GeronimoSecurityPermission SET_CONTEXT = new GeronimoSecurityPermission("setContext"); 062 063 static { 064 password = "secret"; 065 ContextManager.setAlgorithm("HmacSHA1"); 066 } 067 public final static Subject EMPTY = new Subject(); 068 static { 069 EMPTY.setReadOnly(); 070 registerSubject(EMPTY); 071 } 072 073 public static LoginContext login(String realm, CallbackHandler callbackHandler) throws LoginException { 074 Subject subject = new Subject(); 075 LoginContext loginContext = new LoginContext(realm, subject, callbackHandler); 076 loginContext.login(); 077 SubjectId id = ContextManager.registerSubject(subject); 078 IdentificationPrincipal principal = new IdentificationPrincipal(id); 079 subject.getPrincipals().add(principal); 080 return loginContext; 081 } 082 083 public static void logout(LoginContext loginContext) throws LoginException { 084 Subject subject = loginContext.getSubject(); 085 ContextManager.unregisterSubject(subject); 086 loginContext.logout(); 087 } 088 089 public static void setCallers(Subject currentCaller, Subject nextCaller) { 090 SecurityManager sm = System.getSecurityManager(); 091 if (sm != null) sm.checkPermission(SET_CONTEXT); 092 assert currentCaller != null; 093 assert nextCaller != null; 094 Callers newCallers = new Callers(currentCaller, nextCaller); 095 callers.set(newCallers); 096 } 097 098 public static void clearCallers() { 099 callers.set(null); 100 } 101 102 public static Callers getCallers() { 103 SecurityManager sm = System.getSecurityManager(); 104 if (sm != null) sm.checkPermission(GET_CONTEXT); 105 return callers.get(); 106 } 107 108 public static Callers setNextCaller(Subject nextCaller) { 109 SecurityManager sm = System.getSecurityManager(); 110 if (sm != null) sm.checkPermission(SET_CONTEXT); 111 assert nextCaller != null; 112 Callers oldCallers = callers.get(); 113 assert oldCallers != null; 114 Callers newCallers = new Callers(oldCallers.getNextCaller(), nextCaller); 115 callers.set(newCallers); 116 return oldCallers; 117 } 118 119 public static Callers pushNextCaller(Subject nextCaller) { 120 SecurityManager sm = System.getSecurityManager(); 121 if (sm != null) sm.checkPermission(SET_CONTEXT); 122 Callers oldCallers = callers.get(); 123 Subject oldNextCaller = oldCallers == null? null: oldCallers.getNextCaller(); 124 Subject newNextCaller = (nextCaller == null || nextCaller == EMPTY)? oldNextCaller : nextCaller; 125 Callers newCallers = new Callers(oldNextCaller, newNextCaller); 126 callers.set(newCallers); 127 return oldCallers; 128 } 129 130 public static void popCallers(Callers oldCallers) { 131 SecurityManager sm = System.getSecurityManager(); 132 if (sm != null) sm.checkPermission(SET_CONTEXT); 133 callers.set(oldCallers); 134 } 135 136 public static Subject getCurrentCaller() { 137 SecurityManager sm = System.getSecurityManager(); 138 if (sm != null) sm.checkPermission(GET_CONTEXT); 139 140 Callers callers = ContextManager.callers.get(); 141 return callers == null? null: callers.getCurrentCaller(); 142 } 143 144 public static Subject getNextCaller() { 145 SecurityManager sm = System.getSecurityManager(); 146 if (sm != null) sm.checkPermission(GET_CONTEXT); 147 148 Callers callers = ContextManager.callers.get(); 149 return callers == null? null: callers.getNextCaller(); 150 } 151 152 public static AccessControlContext getCurrentContext() { 153 SecurityManager sm = System.getSecurityManager(); 154 if (sm != null) sm.checkPermission(GET_CONTEXT); 155 156 Callers threadLocalCallers = callers.get(); 157 assert threadLocalCallers != null : "No current callers"; 158 Subject currentSubject = threadLocalCallers.getCurrentCaller(); 159 assert currentSubject != null : "No current caller"; 160 Context context = subjectContexts.get(currentSubject); 161 162 assert context != null : "No registered context"; 163 164 return context.context; 165 } 166 167 public static Principal getCurrentPrincipal(Subject callerSubject) { 168 SecurityManager sm = System.getSecurityManager(); 169 if (sm != null) sm.checkPermission(GET_CONTEXT); 170 171 if (callerSubject == null) { 172 return new Principal() { 173 public String getName() { 174 return ""; 175 } 176 }; 177 } 178 Context context = subjectContexts.get(callerSubject); 179 180 assert context != null : "No registered context"; 181 182 return context.principal; 183 } 184 185 public static SubjectId getCurrentId() { 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.id; 198 } 199 200 public static SubjectId getSubjectId(Subject subject) { 201 SecurityManager sm = System.getSecurityManager(); 202 if (sm != null) sm.checkPermission(GET_CONTEXT); 203 204 Context context = subjectContexts.get(subject); 205 206 return (context != null ? context.id : null); 207 } 208 209 public static boolean isCallerInRole(String EJBName, String role) { 210 if (EJBName == null) throw new IllegalArgumentException("EJBName must not be null"); 211 if (role == null) throw new IllegalArgumentException("Role must not be null"); 212 213 try { 214 Callers currentCallers = callers.get(); 215 if (currentCallers == null) { 216 return false; 217 } 218 Subject currentSubject = currentCallers.getCurrentCaller(); 219 if (currentSubject == null) { 220 return false; 221 } 222 223 Context context = subjectContexts.get(currentSubject); 224 225 assert context != null : "No registered context"; 226 227 context.context.checkPermission(new EJBRoleRefPermission(EJBName, role)); 228 } catch (AccessControlException e) { 229 return false; 230 } 231 return true; 232 } 233 234 public static Subject getRegisteredSubject(SubjectId id) { 235 return subjectIds.get(id); 236 } 237 238 public static synchronized SubjectId registerSubject(Subject subject) { 239 SecurityManager sm = System.getSecurityManager(); 240 if (sm != null) sm.checkPermission(SET_CONTEXT); 241 242 if (subject == null) throw new IllegalArgumentException("Subject must not be null"); 243 244 AccessControlContext acc = (AccessControlContext) Subject.doAsPrivileged(subject, new PrivilegedAction() { 245 public Object run() { 246 return AccessController.getContext(); 247 } 248 }, null); 249 250 Context context = new Context(); 251 context.subject = subject; 252 context.context = acc; 253 Set<? extends Principal> principals = subject.getPrincipals(GeronimoCallerPrincipal.class); 254 if (!principals.isEmpty()) { 255 context.principal = principals.iterator().next(); 256 } else if (!(principals = subject.getPrincipals(PrimaryRealmPrincipal.class)).isEmpty()) { 257 context.principal = principals.iterator().next(); 258 } else if (!(principals = subject.getPrincipals(RealmPrincipal.class)).isEmpty()) { 259 context.principal = principals.iterator().next(); 260 } else if (!(principals = subject.getPrincipals()).isEmpty()) { 261 context.principal = principals.iterator().next(); 262 } 263 Long id = nextSubjectId++; 264 try { 265 context.id = new SubjectId(id, hash(id)); 266 } catch (NoSuchAlgorithmException e) { 267 throw new ProviderException("No such algorithm: " + algorithm + ". This can be caused by a misconfigured java.ext.dirs, JAVA_HOME or JRE_HOME environment variable"); 268 } catch (InvalidKeyException e) { 269 throw new ProviderException("Invalid key: " + key.toString()); 270 } 271 subjectIds.put(context.id, subject); 272 subjectContexts.put(subject, context); 273 274 return context.id; 275 } 276 277 public static synchronized void unregisterSubject(Subject subject) { 278 SecurityManager sm = System.getSecurityManager(); 279 if (sm != null) sm.checkPermission(SET_CONTEXT); 280 281 if (subject == null) throw new IllegalArgumentException("Subject must not be null"); 282 283 Context context = subjectContexts.get(subject); 284 if (context == null) return; 285 286 subjectIds.remove(context.id); 287 subjectContexts.remove(subject); 288 } 289 290 /** 291 * Obtain the thread's identifying principal. 292 * <p/> 293 * Clients should use <code>Subject.doAs*</code> to associate a Subject 294 * with the thread's call stack. It is this Subject that will be used for 295 * authentication checks. 296 * <p/> 297 * Return a <code>IdentificationPrincipal</code>. This kind of principal 298 * is inserted into a subject if one uses one of the Geronimo LoginModules. 299 * It is a secure id that identifies the Subject. 300 * 301 * @return the principal that identifies the Subject of this thread. 302 * @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedAction) 303 * @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedExceptionAction) 304 * @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedAction, java.security.AccessControlContext) 305 * @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext) 306 */ 307 public static IdentificationPrincipal getThreadPrincipal() { 308 SecurityManager sm = System.getSecurityManager(); 309 if (sm != null) sm.checkPermission(GET_CONTEXT); 310 311 Subject subject = Subject.getSubject(AccessController.getContext()); 312 if (subject != null) { 313 Set set = subject.getPrincipals(IdentificationPrincipal.class); 314 if (!set.isEmpty()) return (IdentificationPrincipal) set.iterator().next(); 315 } 316 return null; 317 } 318 319 public static String getAlgorithm() { 320 SecurityManager sm = System.getSecurityManager(); 321 if (sm != null) sm.checkPermission(GET_CONTEXT); 322 323 return algorithm; 324 } 325 326 public static void setAlgorithm(String algorithm) { 327 SecurityManager sm = System.getSecurityManager(); 328 if (sm != null) sm.checkPermission(SET_CONTEXT); 329 330 ContextManager.algorithm = algorithm; 331 332 key = new SecretKeySpec(password.getBytes(), algorithm); 333 334 /** 335 * Make sure that we can generate the Mac. 336 */ 337 try { 338 Mac mac = Mac.getInstance(algorithm); 339 mac.init(key); 340 } catch (NoSuchAlgorithmException e) { 341 assert false : "Should never have reached here"; 342 throw new ProviderException("No such algorithm: " + algorithm + ". This can be caused by a misconfigured java.ext.dirs, JAVA_HOME or JRE_HOME environment variable."); 343 } catch (InvalidKeyException e) { 344 assert false : "Should never have reached here"; 345 throw new ProviderException("Invalid key: " + key.toString()); 346 } 347 } 348 349 public static String getPassword() { 350 SecurityManager sm = System.getSecurityManager(); 351 if (sm != null) sm.checkPermission(GET_CONTEXT); 352 353 return password; 354 } 355 356 public static void setPassword(String password) { 357 SecurityManager sm = System.getSecurityManager(); 358 if (sm != null) sm.checkPermission(SET_CONTEXT); 359 360 ContextManager.password = password; 361 362 key = new SecretKeySpec(password.getBytes(), algorithm); 363 } 364 365 private static byte[] hash(Long id) throws NoSuchAlgorithmException, InvalidKeyException { 366 long n = id; 367 byte[] bytes = new byte[8]; 368 for (int i = 7; i >= 0; i--) { 369 bytes[i] = (byte) (n); 370 n >>>= 8; 371 } 372 373 Mac mac = Mac.getInstance(algorithm); 374 mac.init(key); 375 mac.update(bytes); 376 377 return mac.doFinal(); 378 } 379 380 private static class Context { 381 SubjectId id; 382 AccessControlContext context; 383 Subject subject; 384 Principal principal; 385 } 386 387 }