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    }