/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.security;
import java.io.Serializable;
import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.Subject;
import javax.security.jacc.EJBRoleRefPermission;
import org.apache.geronimo.security.realm.providers.GeronimoCallerPrincipal;
/**
* @version $Rev: 355877 $ $Date: 2005-12-11 03:48:27 +0100 (Sun, 11 Dec 2005) $
*/
public class ContextManager {
private static ThreadLocal currentCallerId = new ThreadLocal();
private static ThreadLocal currentCaller = new ThreadLocal();
private static ThreadLocal nextCaller = new ThreadLocal();
private static Map subjectContexts = new IdentityHashMap();
private static Map subjectIds = new Hashtable();
private static long nextSubjectId = System.currentTimeMillis();
private static SecretKey key;
private static String algorithm;
private static String password;
public static final GeronimoSecurityPermission GET_CONTEXT = new GeronimoSecurityPermission("getContext");
public static final GeronimoSecurityPermission SET_CONTEXT = new GeronimoSecurityPermission("setContext");
static {
password = "secret";
ContextManager.setAlgorithm("HmacSHA1");
}
/**
* After a login, the client is left with a relatively empty Subject, while
* the Subject used by the server has more important contents. This method
* lets a server-side component acting as an authentication client (such
* as Tocmat/Jetty) access the fully populated server-side Subject.
*/
public static Subject getServerSideSubject(Subject clientSideSubject) {
Set set = clientSideSubject.getPrincipals(IdentificationPrincipal.class);
if(set == null || set.size() == 0) {
return null;
}
IdentificationPrincipal idp = (IdentificationPrincipal)set.iterator().next();
return getRegisteredSubject(idp.getId());
}
public static void setCurrentCallerId(Serializable id) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
currentCallerId.set(id);
}
public static Serializable getCurrentCallerId() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
return (Serializable) currentCallerId.get();
}
public static void setNextCaller(Subject subject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
nextCaller.set(subject);
}
public static Subject getNextCaller() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
return (Subject) nextCaller.get();
}
public static void setCurrentCaller(Subject subject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
currentCaller.set(subject);
}
public static Subject getCurrentCaller() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
return (Subject) currentCaller.get();
}
public static AccessControlContext getCurrentContext() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
Subject currentSubject = (Subject) currentCaller.get();
assert currentSubject != null : "No current caller";
Context context = (Context) subjectContexts.get(currentSubject);
assert context != null : "No registered context";
return context.context;
}
public static Principal getCurrentPrincipal(Subject callerSubject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
if (callerSubject == null) {
return new Principal() {
public String getName() {
return "";
}
};
}
Context context = (Context) subjectContexts.get(callerSubject);
assert context != null : "No registered context";
return context.principal;
}
public static SubjectId getCurrentId() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
Context context = (Context) subjectContexts.get(currentCaller.get());
assert context != null : "No registered context";
return context.id;
}
public static SubjectId getSubjectId(Subject subject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
Context context = (Context) subjectContexts.get(subject);
return (context != null ? context.id : null);
}
public static boolean isCallerInRole(String EJBName, String role) {
if (EJBName == null) throw new IllegalArgumentException("EJBName must not be null");
if (role == null) throw new IllegalArgumentException("Role must not be null");
try {
Object caller = currentCaller.get();
if (caller == null) return false;
Context context = (Context) subjectContexts.get(currentCaller.get());
assert context != null : "No registered context";
context.context.checkPermission(new EJBRoleRefPermission(EJBName, role));
} catch (AccessControlException e) {
return false;
}
return true;
}
public static Subject getRegisteredSubject(SubjectId id) {
return (Subject) subjectIds.get(id);
}
public static synchronized SubjectId registerSubject(Subject subject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
if (subject == null) throw new IllegalArgumentException("Subject must not be null");
AccessControlContext acc = (AccessControlContext) Subject.doAsPrivileged(subject, new PrivilegedAction() {
public Object run() {
return AccessController.getContext();
}
}, null);
Context context = new Context();
context.subject = subject;
context.context = acc;
Set principals = subject.getPrincipals(GeronimoCallerPrincipal.class);
if (!principals.isEmpty()) {
context.principal = (Principal) principals.iterator().next();
} else if (!(principals = subject.getPrincipals(PrimaryRealmPrincipal.class)).isEmpty()) {
context.principal = (PrimaryRealmPrincipal) principals.iterator().next();
} else if (!(principals = subject.getPrincipals(RealmPrincipal.class)).isEmpty()) {
context.principal = (RealmPrincipal) principals.iterator().next();
} else if (!(principals = subject.getPrincipals()).isEmpty()) {
context.principal = (Principal) principals.iterator().next();
}
Long id = new Long(nextSubjectId++);
context.id = new SubjectId(id, hash(id));
subjectIds.put(context.id, subject);
subjectContexts.put(subject, context);
return context.id;
}
public static synchronized void unregisterSubject(Subject subject) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
if (subject == null) throw new IllegalArgumentException("Subject must not be null");
Context context = (Context) subjectContexts.get(subject);
if (context == null) return;
subjectIds.remove(context.id);
subjectContexts.remove(subject);
}
/**
* Obtain the thread's identifying principal.
* <p/>
* Clients should use <code>Subject.doAs*</code> to associate a Subject
* with the thread's call stack. It is this Subject that will be used for
* authentication checks.
* <p/>
* Return a <code>IdentificationPrincipal</code>. This kind of principal
* is inserted into a subject if one uses one of the Geronimo LoginModules.
* It is a secure id that identifies the Subject.
*
* @return the principal that identifies the Subject of this thread.
* @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedAction)
* @see Subject#doAs(javax.security.auth.Subject, java.security.PrivilegedExceptionAction)
* @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedAction, java.security.AccessControlContext)
* @see Subject#doAsPrivileged(javax.security.auth.Subject, java.security.PrivilegedExceptionAction, java.security.AccessControlContext)
*/
public static IdentificationPrincipal getThreadPrincipal() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
Subject subject = Subject.getSubject(AccessController.getContext());
if (subject != null) {
Set set = subject.getPrincipals(IdentificationPrincipal.class);
if (!set.isEmpty()) return (IdentificationPrincipal) set.iterator().next();
}
return null;
}
public static String getAlgorithm() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
return algorithm;
}
public static void setAlgorithm(String algorithm) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
ContextManager.algorithm = algorithm;
key = new SecretKeySpec(password.getBytes(), algorithm);
/**
* Make sure that we can generate the Mac.
*/
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
} catch (NoSuchAlgorithmException e) {
assert false : "Should never have reached here";
} catch (InvalidKeyException e) {
assert false : "Should never have reached here";
}
}
public static String getPassword() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(GET_CONTEXT);
return password;
}
public static void setPassword(String password) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(SET_CONTEXT);
ContextManager.password = password;
key = new SecretKeySpec(password.getBytes(), algorithm);
}
private static byte[] hash(Long id) {
long n = id.longValue();
byte[] bytes = new byte[8];
for (int i = 7; i >= 0; i--) {
bytes[i] = (byte) (n);
n >>>= 8;
}
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
mac.update(bytes);
return mac.doFinal();
} catch (NoSuchAlgorithmException e) {
} catch (InvalidKeyException e) {
}
assert false : "Should never have reached here";
return null;
}
private static class Context {
SubjectId id;
AccessControlContext context;
Subject subject;
Principal principal;
}
}