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    }