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