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 }