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