001: /*
002: * JOSSO: Java Open Single Sign-On
003: *
004: * Copyright 2004-2008, Atricore, Inc.
005: *
006: * This is free software; you can redistribute it and/or modify it
007: * under the terms of the GNU Lesser General Public License as
008: * published by the Free Software Foundation; either version 2.1 of
009: * the License, or (at your option) any later version.
010: *
011: * This software is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this software; if not, write to the Free
018: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
020: */
021:
022: package org.josso.jb42.agent;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026: import org.jboss.naming.Util;
027: import org.jboss.security.AuthenticationManager;
028: import org.jboss.security.RealmMapping;
029: import org.jboss.security.SimplePrincipal;
030: import org.jboss.security.SubjectSecurityManager;
031: import org.jboss.security.plugins.JaasSecurityManager; //import org.jboss.util.CachePolicy;
032: import org.jboss.web.tomcat.security.JBossSecurityMgrRealm;
033: import org.jboss.web.tomcat.security.SecurityAssociationValve;
034: import org.josso.gateway.identity.SSOUser;
035: import org.josso.tc55.agent.jaas.CatalinaSSOUser;
036:
037: import javax.naming.Context;
038: import javax.naming.InitialContext;
039: import javax.naming.LinkRef;
040: import javax.naming.NamingException;
041: import javax.security.auth.Subject;
042: import java.lang.reflect.Method;
043: import java.security.Principal;
044: import java.security.cert.X509Certificate;
045: import java.util.Iterator;
046: import java.util.Set;
047:
048: /**
049: * JBoss Realm proxy that does mainly the following two things :
050: *
051: * <p>
052: * 1. Before invoking the overriden Realm methods, it creates a "java:comp/env/security" JNDI context
053: * needed by the JBossSecurityMgrRealm to retrieve the configured JBoss Security Manager.
054: * The "java:comp/env/security" context is only created by Catalina for built-in authenticators
055: * and web applications contexts. The Context where the Agent Valve is associated to does not have
056: * an ENC at all so we must build one for it.
057: * <p>
058: * 2. Completely overrides the user authentication method so that the current Principal is not
059: * the SSO Session Id Principal but the SSOUser Principal.
060: *
061: * <p>
062: * All Realm operations that require a SecurityContext were overriden so that there is a chance
063: * for our Realm to prepare the "java:comp/env/security" JNDI Context.
064: * <p>
065: *
066: * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
067: * @version CVS $Id: JBossCatalinaRealm.java 384 2006-06-09 13:51:36Z sgonzalez $
068: */
069: public class JBossCatalinaRealm extends JBossSecurityMgrRealm {
070: private static final Log logger = LogFactory
071: .getLog(JBossCatalinaRealm.class);
072:
073: /** The fixed JOSSO JBoss Security Domain Name */
074: private static final String JOSSO_SECURITY_DOMAIN = "java:/jaas/josso";
075:
076: private static final String DEFAULT_CACHE_POLICY_PATH = "java:/timedCacheFactory";
077:
078: /** The location of the security credential cache policy. This is first treated
079: as a ObjectFactory location that is capable of returning CachePolicy instances
080: on a per security domain basis by appending a '/security-domain-name' string
081: to this name when looking up the CachePolicy for a domain. If this fails then
082: the location is treated as a single CachePolicy for all security domains.
083: */
084: private static String cacheJndiName = DEFAULT_CACHE_POLICY_PATH;
085:
086: /**
087: * Return the Principal associated with the specified username and
088: * credentials, if there is one; otherwise return null.
089: *
090: * The method was completely rewritten since the overriden operation,
091: * on succesfull authentication, sets as the authenticated Principal
092: * a SimplePrincipal instantiated using the provided username.
093: * The problem is that in JOSSO the username is a SSO Session Id, not
094: * a username. So we need to set the SSOUser returned by the JAAS Gateway
095: * Login Module as the authenticatd Principal.
096: * Since the JaasSecurityManager caches the authenticated user using the
097: * Principal referring to a JOSSO Session Id, we will need to map, for
098: * example when roles are checked against the realm, a user Principal
099: * back to its JOSSO Session Identifier Principal. This way the the user
100: * and its roles can be retrieved correctly by the JaasSecurityManager.
101: *
102: * @param username Username of the Principal to look up
103: * @param credentials Password or other credentials to use in
104: * authenticating this username
105: */
106: public Principal authenticate(String username, String credentials) {
107:
108: logger.debug("Begin authenticate, username=" + username);
109:
110: Principal principal = null;
111: SSOUser ssoUser = null;
112: Principal caller = (Principal) SecurityAssociationValve.userPrincipal
113: .get();
114: if (caller == null && username == null && credentials == null)
115: return null;
116:
117: try {
118: Context securityCtx = null;
119: securityCtx = prepareENC();
120:
121: if (securityCtx == null) {
122: logger
123: .error("No security context for authenticate(String, String)");
124: return null;
125: }
126:
127: // Get the JBoss security manager from the ENC context
128: SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx
129: .lookup("securityMgr");
130: principal = new SimplePrincipal(username);
131: char[] passwordChars = null;
132: if (credentials != null)
133: passwordChars = credentials.toCharArray();
134: Subject subject = new Subject();
135: if (securityMgr.isValid(principal, credentials, subject)) {
136: logger.debug("User: " + username + " is authenticated");
137:
138: // Get the authorized subject set by the isValid() call on succesful
139: // authentication.
140: //Subject activeSubject = securityMgr.getActiveSubject();
141:
142: logger.trace("Authenticated Subject: " + subject);
143:
144: Set principals = subject.getPrincipals(SSOUser.class);
145: Iterator i = principals.iterator();
146: while (i.hasNext()) {
147: ssoUser = (SSOUser) i.next();
148: break;
149: }
150:
151: // Make the cache aware of the user-session association so that
152: // it can handle correctly cache entry lookups.
153: //_cachePolicy.attachSessionToUser(principal, ssoUser);
154:
155: // Instead of associating the Principal used for authenticating (which is a
156: // session id), sets the authenticated principal to the SSOUser part of the
157: // Subject returned by the Gateway.
158: JBossSecurityAssociationActions.setPrincipalInfo(
159: ssoUser, passwordChars, subject);
160:
161: // Get the CallerPrincipal mapping
162: RealmMapping rm = (RealmMapping) securityCtx
163: .lookup("realmMapping");
164: Principal oldPrincipal = ssoUser;
165: principal = rm.getPrincipal(oldPrincipal);
166: logger.trace("Mapped from input principal: "
167: + oldPrincipal + " to: " + principal);
168:
169: // Get the caching principal
170: principal = getCachingPrincpal(rm, oldPrincipal,
171: principal, credentials, subject);
172:
173: } else {
174: principal = null;
175: logger.trace("User: " + username
176: + " is NOT authenticated");
177: }
178: } catch (NamingException e) {
179: principal = null;
180: logger.error("Error during authenticate", e);
181: }
182: logger.trace("End authenticate, principal=" + ssoUser);
183: return ssoUser;
184: }
185:
186: /**
187: * Return <code>true</code> if the specified Principal has the specified
188: * security role, within the context of this Realm; otherwise return
189: * <code>false</code>.
190: *
191: * Since the Principal, in the JaasSecurityManager, has been stored in its cache
192: * using the JOSSO Single Sign-On Session Identifier Principal (see isValid method),
193: * when roles are checked , the Principal to be submitted to the overriden
194: * operation is not the user principal but the JOSSO Session Id Principal.
195: *
196: * @param principal Principal for whom the role is to be checked
197: * @param role Security role to be checked
198: */
199: public boolean hasRole(Principal principal, String role) {
200: boolean hasRole = false;
201:
202: try {
203: Context securityCtx = null;
204: securityCtx = prepareENC();
205: if (securityCtx == null) {
206: logger
207: .error("No security context for authenticate(String, String)");
208: return false;
209: }
210:
211: logger.debug("hasRole(" + principal + "," + role + ")");
212:
213: // A nasty way to detect wether we have a version previous to 4.0.4.GA ...
214: try {
215:
216: // Get the JBoss security manager from the ENC context
217: SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx
218: .lookup("securityMgr");
219: Subject activeSubject = securityMgr.getActiveSubject();
220:
221: logger.debug("Authenticated Subject: " + activeSubject);
222:
223: CatalinaSSOUser ssoUser = CatalinaSSOUser.newInstance(
224: this , activeSubject);
225: hasRole = super .hasRole(ssoUser, role);
226:
227: } catch (NullPointerException npe) {
228:
229: // This is a JBoss 4 version prior to 4.0.4.GA
230:
231: // Just in case ...
232: if (logger.isDebugEnabled())
233: logger.debug(npe);
234:
235: hasRole = super .hasRole(principal, role);
236:
237: }
238:
239: } catch (NamingException e) {
240: principal = null;
241: logger.error("Error during authenticate", e);
242: }
243:
244: return hasRole;
245: }
246:
247: /**
248: * Return the Principal associated with the specified chain of X509
249: * client certificates. If there is none, return <code>null</code>.
250: *
251: * Before invoking the overriden operation it creates the security JNDI context
252: * in case one was not found.
253: *
254: * @param certs Array of client certificates, with the first one in
255: * the array being the certificate of the client itself.
256: */
257: public Principal authenticate(X509Certificate[] certs) {
258: logger.trace("authenticate(X509Certificate[]), Begin");
259:
260: try {
261: prepareENC();
262: return super .authenticate(certs);
263: } catch (NamingException ne) {
264: // Error creating ENC Context
265: logger.error("Cannot create ENC Context");
266: }
267:
268: logger.debug("authenticate(), Emd");
269: return null;
270: }
271:
272: /** This creates a java:comp/env/security context that contains a
273: securityMgr binding pointing to an AuthenticationManager implementation
274: and a realmMapping binding pointing to a RealmMapping implementation.
275: */
276: protected Context prepareENC() throws NamingException {
277:
278: ClassLoader loader = Thread.currentThread()
279: .getContextClassLoader();
280:
281: InitialContext iniCtx = new InitialContext();
282:
283: boolean securityContextExists = false;
284: boolean isJaasSecurityManager = false;
285: try {
286: Context envCtx = (Context) iniCtx.lookup("java:comp/env");
287: Context securityCtx = (Context) envCtx.lookup("security");
288: securityContextExists = true;
289:
290: AuthenticationManager securityMgr = (AuthenticationManager) securityCtx
291: .lookup("securityMgr");
292:
293: // If the Security Manager set in the web application ENC is not
294: // a JaasSecurityManager, unbind the Security context and rebind it
295: // with the JaasSecurityManager associated with the JOSSO Security Domain.
296: // Note: the jboss-web.xml file of the partner application MUST not have an
297: // entry referring to a security domain.
298: if (!(securityMgr instanceof JaasSecurityManager)) {
299: Util.unbind(envCtx, "security");
300: } else
301: isJaasSecurityManager = true;
302: } catch (NamingException e) {
303: // No Security Context found
304: }
305:
306: // If we do not have a SecurityContext create it
307: Context envCtx = null;
308: if (!securityContextExists) {
309: Thread currentThread = Thread.currentThread();
310: logger.debug("Creating ENC using ClassLoader: " + loader);
311: ClassLoader parent = loader.getParent();
312: while (parent != null) {
313: logger.debug(".." + parent);
314: parent = parent.getParent();
315: }
316:
317: envCtx = (Context) iniCtx.lookup("java:comp");
318: envCtx = envCtx.createSubcontext("env");
319: } else
320: envCtx = (Context) iniCtx.lookup("java:comp/env");
321:
322: // If the Security Manager binded is not a JaasSecurityManager, rebind using
323: // the Security Manager associated with the JOSSO Security Domain.
324: if (!isJaasSecurityManager) {
325: // Prepare the Security JNDI subcontext
326: logger.debug("Linking security/securityMgr to JNDI name: "
327: + JOSSO_SECURITY_DOMAIN);
328: Util.bind(envCtx, "security/securityMgr", new LinkRef(
329: JOSSO_SECURITY_DOMAIN));
330: Util.bind(envCtx, "security/realmMapping", new LinkRef(
331: JOSSO_SECURITY_DOMAIN));
332: Util.bind(envCtx, "security/security-domain", new LinkRef(
333: JOSSO_SECURITY_DOMAIN));
334: Util.bind(envCtx, "security/subject", new LinkRef(
335: JOSSO_SECURITY_DOMAIN + "/subject"));
336: }
337:
338: logger.debug("JBossCatalinaRealm.prepareENC, End");
339:
340: return (Context) iniCtx.lookup("java:comp/env/security");
341: }
342:
343: /** Lookup the authentication CachePolicy object for a security domain. This
344: method first treats the cacheJndiName as a ObjectFactory location that is
345: capable of returning CachePolicy instances on a per security domain basis
346: by appending a '/security-domain-name' string to the cacheJndiName when
347: looking up the CachePolicy for a domain. If this fails then the cacheJndiName
348: location is treated as a single CachePolicy for all security domains.
349: @deprecated No longer used for JBoss 3.2.6 support
350:
351: private static CachePolicy lookupCachePolicy(String securityDomain) {
352: CachePolicy authCache = null;
353: String domainCachePath = cacheJndiName + '/' + securityDomain;
354: try {
355: InitialContext iniCtx = new InitialContext();
356: authCache = (CachePolicy) iniCtx.lookup(domainCachePath);
357: } catch (Exception e) {
358: // Failed, treat the cacheJndiName name as a global CachePolicy binding
359: try {
360: InitialContext iniCtx = new InitialContext();
361: authCache = (CachePolicy) iniCtx.lookup(cacheJndiName);
362: } catch (Exception e2) {
363: logger.warn("Failed to locate auth CachePolicy at: " + cacheJndiName
364: + " for securityDomain=" + securityDomain);
365: }
366: }
367: return authCache;
368: }
369: */
370: /** Use reflection to attempt to set the authentication cache on the
371: * securityMgr argument.
372: *
373: * This is done this way to avoid dependency with JaasSecurityManager.
374: *
375: * @deprecated No longer used for JBoss 3.2.6 support
376: * @param securityMgr the security manager
377: * @param cachePolicy the cache policy implementation
378:
379: private static void setSecurityDomainCache(AuthenticationManager securityMgr,
380: CachePolicy cachePolicy) {
381: try {
382: Class[] setCachePolicyTypes = {CachePolicy.class};
383: Method m = securityMgr.getClass().getMethod("setCachePolicy", setCachePolicyTypes);
384: Object[] setCachePolicyArgs = {cachePolicy};
385: m.invoke(securityMgr, setCachePolicyArgs);
386: logger.debug("setCachePolicy, c=" + setCachePolicyArgs[0]);
387: } catch (Exception e2) { // No cache policy support, this is ok
388: logger.debug("setCachePolicy failed", e2);
389: }
390: }
391: */
392: }
|