KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > web > tomcat > security > JBossSecurityMgrRealm


1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */

22 package org.jboss.web.tomcat.security;
23
24 import java.security.Principal JavaDoc;
25 import java.security.cert.X509Certificate JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.Set JavaDoc;
29 import java.util.HashSet JavaDoc;
30 import java.io.IOException JavaDoc;
31 import javax.naming.Context JavaDoc;
32 import javax.naming.InitialContext JavaDoc;
33 import javax.naming.NamingException JavaDoc;
34 import javax.security.auth.Subject JavaDoc;
35 import javax.servlet.http.HttpServletResponse JavaDoc;
36
37 import org.apache.catalina.LifecycleException;
38 import org.apache.catalina.Realm;
39 import org.apache.catalina.deploy.SecurityConstraint;
40 import org.apache.catalina.deploy.LoginConfig;
41 import org.apache.catalina.connector.Request;
42 import org.apache.catalina.connector.Response;
43 import org.apache.catalina.realm.RealmBase;
44 import org.apache.catalina.realm.Constants;
45 import org.apache.catalina.realm.GenericPrincipal;
46 import org.jboss.logging.Logger;
47 import org.jboss.security.CertificatePrincipal;
48 import org.jboss.security.RealmMapping;
49 import org.jboss.security.SimplePrincipal;
50 import org.jboss.security.SubjectSecurityManager;
51 import org.jboss.security.auth.certs.SubjectDNMapping;
52 import org.jboss.security.auth.callback.CallbackHandlerPolicyContextHandler;
53
54 /**
55  * An implementation of the catelinz Realm and Valve interfaces. The Realm
56  * implementation handles authentication and authorization using the JBossSX
57  * security framework. It relieas on the JNDI ENC namespace setup by the
58  * AbstractWebContainer. In particular, it uses the java:comp/env/security
59  * subcontext to access the security manager interfaces for authorization and
60  * authenticaton. <p/> The Valve interface is used to associated the
61  * authenticated user with the SecurityAssociation class when a request begins
62  * so that web components may call EJBs and have the principal propagated. The
63  * security association is removed when the request completes.
64  *
65  * @author Scott.Stark@jboss.org
66  * @version $Revision: 43839 $
67  * @see org.jboss.security.AuthenticationManager
68  * @see org.jboss.security.CertificatePrincipal
69  * @see org.jboss.security.RealmMapping
70  * @see org.jboss.security.SimplePrincipal
71  * @see org.jboss.security.SecurityAssociation
72  * @see org.jboss.security.SubjectSecurityManager
73  */

74 public class JBossSecurityMgrRealm extends RealmBase implements Realm
75 {
76    static Logger log = Logger.getLogger(JBossSecurityMgrRealm.class);
77    /**
78     * The converter from X509 cert chain to Princpal
79     */

80    private CertificatePrincipal certMapping = new SubjectDNMapping();
81    /**
82     * The JBossSecurityMgrRealm category trace flag
83     */

84    private boolean trace;
85    /** The mode for handling the all roles mode of role-name=* */
86    private AllRolesMode allRolesMode = AllRolesMode.AUTH_ONLY_MODE;
87
88    /**
89     * Set the class name of the CertificatePrincipal used for mapping X509 cert
90     * chains to a Princpal.
91     *
92     * @param className the CertificatePrincipal implementation class that must
93     * have a no-arg ctor.
94     * @see org.jboss.security.CertificatePrincipal
95     */

96    public void setCertificatePrincipal(String JavaDoc className)
97    {
98       try
99       {
100          ClassLoader JavaDoc loader = Thread.currentThread().getContextClassLoader();
101          Class JavaDoc cpClass = loader.loadClass(className);
102          certMapping = (CertificatePrincipal) cpClass.newInstance();
103       }
104       catch (Exception JavaDoc e)
105       {
106          log.error("Failed to load CertificatePrincipal: " + className, e);
107          certMapping = new SubjectDNMapping();
108       }
109    }
110
111    private Context JavaDoc getSecurityContext()
112    {
113       Context JavaDoc securityCtx = null;
114       // Get the JBoss security manager from the ENC context
115
try
116       {
117          InitialContext JavaDoc iniCtx = new InitialContext JavaDoc();
118          securityCtx = (Context JavaDoc) iniCtx.lookup("java:comp/env/security");
119       }
120       catch (NamingException JavaDoc e)
121       {
122          // Apparently there is no security context?
123
}
124       return securityCtx;
125    }
126
127    /**
128     * Override to allow a single realm to be shared as a realm and valve
129     */

130    public void start() throws LifecycleException
131    {
132       if (super.started == true)
133       {
134          return;
135       }
136       super.start();
137       trace = log.isTraceEnabled();
138    }
139
140    /**
141     * Override to allow a single realm to be shared as a realm and valve
142     */

143    public void stop() throws LifecycleException
144    {
145       if (super.started == false)
146       {
147          return;
148       }
149       super.stop();
150    }
151
152    public boolean hasResourcePermission(Request request, Response JavaDoc response,
153       SecurityConstraint[] constraints, org.apache.catalina.Context context)
154       throws IOException JavaDoc
155    {
156       if (constraints == null || constraints.length == 0)
157       {
158          return (true);
159       }
160
161       boolean hasPermission = false;
162       // Specifically allow access to the form login and form error pages
163
// and the "j_security_check" action
164
LoginConfig config = context.getLoginConfig();
165       if ((config != null) &&
166          (Constants.FORM_METHOD.equals(config.getAuthMethod())))
167       {
168          String JavaDoc requestURI = request.getRequestPathMB().toString();
169          String JavaDoc loginPage = config.getLoginPage();
170          if (loginPage.equals(requestURI))
171          {
172             if( trace )
173                log.trace("Allow access to login page " + loginPage);
174             return (true);
175          }
176          String JavaDoc errorPage = config.getErrorPage();
177          if (errorPage.equals(requestURI))
178          {
179             if( trace )
180                log.trace("Allow access to error page " + errorPage);
181             return (true);
182          }
183          if (requestURI.endsWith(Constants.FORM_ACTION))
184          {
185             if( trace )
186                log.trace("Allow access to username/password submission");
187             return (true);
188          }
189       }
190
191       // Which user principal have we already authenticated?
192
Principal JavaDoc principal = request.getPrincipal();
193       boolean denyfromall = false;
194       for (int i = 0; i < constraints.length; i++)
195       {
196          SecurityConstraint constraint = constraints[i];
197
198          String JavaDoc roles[];
199          if (constraint.getAllRoles())
200          {
201             // * means all roles defined in web.xml
202
roles = request.getContext().findSecurityRoles();
203          }
204          else
205          {
206             roles = constraint.findAuthRoles();
207          }
208
209          if (roles == null)
210          {
211             roles = new String JavaDoc[0];
212          }
213
214          if( trace )
215             log.trace("Checking roles " + principal);
216
217          if (roles.length == 0 && !constraint.getAllRoles())
218          {
219             if (constraint.getAuthConstraint())
220             {
221                if( trace )
222                   log.trace("No roles");
223                hasPermission = false; // No listed roles means no access at all
224
denyfromall = true;
225             }
226             else
227             {
228                if( trace )
229                   log.trace("Passing all access");
230                return (true);
231             }
232          }
233          else if (principal == null)
234          {
235             if( trace )
236                log.trace("No user authenticated, cannot grant access");
237             hasPermission = false;
238          }
239          else if (!denyfromall)
240          {
241             for (int j = 0; j < roles.length; j++)
242             {
243                if (hasRole(principal, roles[j]))
244                {
245                   hasPermission = true;
246                }
247                if( trace )
248                   log.trace("No role found: " + roles[j]);
249             }
250          }
251       }
252
253       if (allRolesMode != AllRolesMode.STRICT_MODE
254          && hasPermission == false
255          && principal != null)
256       {
257          if (trace)
258          {
259             log.trace("Checking for all roles mode: " + allRolesMode);
260          }
261          // Check for an all roles(role-name="*")
262
for (int i = 0; i < constraints.length; i++)
263          {
264             SecurityConstraint constraint = constraints[i];
265             String JavaDoc roles[];
266             // If the all roles mode exists, sets
267
if (constraint.getAllRoles())
268             {
269                if (allRolesMode == AllRolesMode.AUTH_ONLY_MODE)
270                {
271                   if (trace)
272                   {
273                      log.trace("Granting access for role-name=*, auth-only");
274                   }
275                   hasPermission = true;
276                   break;
277                }
278
279                // For AllRolesMode.STRICT_AUTH_ONLY_MODE there must be zero roles
280
roles = request.getContext().findSecurityRoles();
281                if (roles.length == 0 && allRolesMode == AllRolesMode.STRICT_AUTH_ONLY_MODE)
282                {
283                   if (trace)
284                   {
285                      log.trace("Granting access for role-name=*, strict auth-only");
286                   }
287                   hasPermission = true;
288                   break;
289                }
290             }
291          }
292       }
293
294       // Return a "Forbidden" message denying access to this resource
295
if (!hasPermission)
296       {
297          response.sendError
298             (HttpServletResponse.SC_FORBIDDEN,
299                sm.getString("realmBase.forbidden"));
300       }
301       return hasPermission;
302    }
303
304    /**
305     * Return the Principal associated with the specified chain of X509 client
306     * certificates. If there is none, return <code>null</code>.
307     *
308     * @param certs Array of client certificates, with the first one in the array
309     * being the certificate of the client itself.
310     */

311    public Principal JavaDoc authenticate(X509Certificate JavaDoc[] certs)
312    {
313       Principal JavaDoc principal = null;
314       Context JavaDoc securityCtx = getSecurityContext();
315       if (securityCtx == null)
316       {
317          if (trace)
318          {
319             log.trace("No security context for authenticate(X509Certificate[])");
320          }
321          return null;
322       }
323
324       try
325       {
326          // Get the JBoss security manager from the ENC context
327
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
328          Subject JavaDoc subject = new Subject JavaDoc();
329          principal = certMapping.toPrinicipal(certs);
330          if (securityMgr.isValid(principal, certs, subject))
331          {
332             if (trace)
333             {
334                log.trace("User: " + principal + " is authenticated");
335             }
336             SecurityAssociationActions.setPrincipalInfo(principal, certs, subject);
337             // Get the CallerPrincipal mapping
338
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
339             Principal JavaDoc oldPrincipal = principal;
340             principal = realmMapping.getPrincipal(oldPrincipal);
341             if (trace)
342             {
343                log.trace("Mapped from input principal: " + oldPrincipal
344                   + "to: " + principal);
345             }
346             // Get the caching principal
347
principal = getCachingPrincpal(realmMapping, oldPrincipal,
348                principal, certs, subject);
349          }
350          else
351          {
352             if (trace)
353             {
354                log.trace("User: " + principal + " is NOT authenticated");
355             }
356             principal = null;
357          }
358       }
359       catch (NamingException JavaDoc e)
360       {
361          log.error("Error during authenticate", e);
362       }
363       return principal;
364    }
365
366    /**
367     * Return the Principal associated with the specified username, which matches
368     * the digest calculated using the given parameters using the method
369     * described in RFC 2069; otherwise return <code>null</code>.
370     *
371     * @param username Username of the Principal to look up
372     * @param digest Digest which has been submitted by the client
373     * @param nonce Unique (or supposedly unique) token which has been used for
374     * this request
375     * @param nc client nonce reuse count
376     * @param cnonce client token
377     * @param qop quality of protection
378     * @param realm Realm name
379     * @param md5a2 Second MD5 digest used to calculate the digest : MD5(Method +
380     * ":" + uri)
381     */

382    public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc digest, String JavaDoc nonce,
383       String JavaDoc nc, String JavaDoc cnonce, String JavaDoc qop, String JavaDoc realm, String JavaDoc md5a2)
384    {
385       Principal JavaDoc principal = null;
386       Context JavaDoc securityCtx = getSecurityContext();
387       if (securityCtx == null)
388       {
389          if (trace)
390          {
391             log.trace("No security context for authenticate(String, String)");
392          }
393          return null;
394       }
395
396       Principal JavaDoc caller = (Principal JavaDoc) SecurityAssociationValve.userPrincipal.get();
397       if (caller == null && username == null && digest == null)
398       {
399          return null;
400       }
401
402       try
403       {
404          DigestCallbackHandler handler = new DigestCallbackHandler(username, nonce,
405             nc, cnonce, qop, realm, md5a2);
406          CallbackHandlerPolicyContextHandler.setCallbackHandler(handler);
407
408          // Get the JBoss security manager from the ENC context
409
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
410          principal = new SimplePrincipal(username);
411          Subject JavaDoc subject = new Subject JavaDoc();
412          if (securityMgr.isValid(principal, digest, subject))
413          {
414             log.trace("User: " + username + " is authenticated");
415             SecurityAssociationActions.setPrincipalInfo(principal, digest, subject);
416             // Get the CallerPrincipal mapping
417
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
418             Principal JavaDoc oldPrincipal = principal;
419             principal = realmMapping.getPrincipal(oldPrincipal);
420             if (trace)
421             {
422                log.trace("Mapped from input principal: " + oldPrincipal
423                   + "to: " + principal);
424             }
425             // Get the caching principal
426
principal = getCachingPrincpal(realmMapping, oldPrincipal,
427                principal, digest, subject);
428          }
429          else
430          {
431             principal = null;
432             if (trace)
433             {
434                log.trace("User: " + username + " is NOT authenticated");
435             }
436          }
437       }
438       catch (NamingException JavaDoc e)
439       {
440          principal = null;
441          log.error("Error during authenticate", e);
442       }
443       finally
444       {
445          CallbackHandlerPolicyContextHandler.setCallbackHandler(null);
446       }
447       if (trace)
448       {
449          log.trace("End authenticate, principal=" + principal);
450       }
451       return principal;
452    }
453
454    /**
455     * Return the Principal associated with the specified username and
456     * credentials, if there is one; otherwise return <code>null</code>.
457     *
458     * @param username Username of the Principal to look up
459     * @param credentials Password or other credentials to use in authenticating
460     * this username
461     */

462    public Principal JavaDoc authenticate(String JavaDoc username, String JavaDoc credentials)
463    {
464       if (trace)
465       {
466          log.trace("Begin authenticate, username=" + username);
467       }
468       Principal JavaDoc principal = null;
469       Context JavaDoc securityCtx = getSecurityContext();
470       if (securityCtx == null)
471       {
472          if (trace)
473          {
474             log.trace("No security context for authenticate(String, String)");
475          }
476          return null;
477       }
478
479       Principal JavaDoc caller = (Principal JavaDoc) SecurityAssociationValve.userPrincipal.get();
480       if (caller == null && username == null && credentials == null)
481       {
482          return null;
483       }
484
485       try
486       {
487          // Get the JBoss security manager from the ENC context
488
SubjectSecurityManager securityMgr = (SubjectSecurityManager) securityCtx.lookup("securityMgr");
489          principal = new SimplePrincipal(username);
490          Subject JavaDoc subject = new Subject JavaDoc();
491          if (securityMgr.isValid(principal, credentials, subject))
492          {
493             log.trace("User: " + username + " is authenticated");
494             SecurityAssociationActions.setPrincipalInfo(principal, credentials, subject);
495             // Get the CallerPrincipal mapping
496
RealmMapping realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
497             Principal JavaDoc oldPrincipal = principal;
498             principal = realmMapping.getPrincipal(oldPrincipal);
499             if (trace)
500             {
501                log.trace("Mapped from input principal: " + oldPrincipal
502                   + "to: " + principal);
503             }
504             // Get the caching principal
505
principal = getCachingPrincpal(realmMapping, oldPrincipal,
506                principal, credentials, subject);
507          }
508          else
509          {
510             principal = null;
511             if (trace)
512             {
513                log.trace("User: " + username + " is NOT authenticated");
514             }
515          }
516       }
517       catch (NamingException JavaDoc e)
518       {
519          principal = null;
520          log.error("Error during authenticate", e);
521       }
522       if (trace)
523       {
524          log.trace("End authenticate, principal=" + principal);
525       }
526       return principal;
527    }
528
529    /**
530     * Returns <code>true</code> if the specified user <code>Principal</code> has
531     * the specified security role, within the context of this
532     * <code>Realm</code>; otherwise return <code>false</code>. This will be true
533     * when an associated role <code>Principal</code> can be found whose
534     * <code>getName</code> method returns a <code>String</code> equalling the
535     * specified role.
536     *
537     * @param principal <code>Principal</code> for whom the role is to be
538     * checked
539     * @param role Security role to be checked
540     */

541    public boolean hasRole(Principal JavaDoc principal, String JavaDoc role)
542    {
543       return super.hasRole(principal, role);
544       /*
545       if ((principal == null) || (role == null))
546       {
547          return false;
548       }
549       if (principal instanceof JBossGenericPrincipal)
550       {
551          return super.hasRole(principal, role);
552       }
553       JBossGenericPrincipal gp = (JBossGenericPrincipal) roleMap.get(principal);
554       Set userRoles = gp.getUserRoles();
555       if (userRoles != null)
556       {
557          Iterator iter = userRoles.iterator();
558          while (iter.hasNext())
559          {
560             Principal p = (Principal) iter.next();
561             if (role.equals(p.getName()))
562             {
563                return true;
564             }
565          }
566       }
567       return false;
568       */

569    }
570
571    /**
572     * Return the Principal associated with the specified username and
573     * credentials, if there is one; otherwise return <code>null</code>.
574     *
575     * @param username Username of the Principal to look up
576     * @param credentials Password or other credentials to use in authenticating
577     * this username
578     */

579    public Principal JavaDoc authenticate(String JavaDoc username, byte[] credentials)
580    {
581       return authenticate(username, new String JavaDoc(credentials));
582    }
583
584    /**
585     * Return a short name for this Realm implementation, for use in log
586     * messages.
587     */

588    protected String JavaDoc getName()
589    {
590       return getClass().getName();
591    }
592
593    /**
594     * Return the password associated with the given principal's user name.
595     */

596    protected String JavaDoc getPassword(String JavaDoc username)
597    {
598       String JavaDoc password = null;
599       return password;
600    }
601
602    /**
603     * Return the Principal associated with the given user name.
604     */

605    protected Principal JavaDoc getPrincipal(String JavaDoc username)
606    {
607       return new SimplePrincipal(username);
608    }
609
610    /**
611     * Access the set of role Princpals associated with the given caller princpal.
612     *
613     * @param principal - the Principal mapped from the authentication principal
614     * and visible from the HttpServletRequest.getUserPrincipal
615     * @return a possible null Set<Principal> for the caller roles
616     */

617    protected Set JavaDoc getPrincipalRoles(Principal JavaDoc principal)
618    {
619       if( (principal instanceof GenericPrincipal) == false )
620          throw new IllegalStateException JavaDoc("Expected GenericPrincipal, but saw: "+principal.getClass());
621       GenericPrincipal gp = (GenericPrincipal) principal;
622       String JavaDoc[] roleNames = gp.getRoles();
623       Set JavaDoc userRoles = new HashSet JavaDoc();
624       if( roleNames != null )
625       {
626          for(int n = 0; n < roleNames.length; n ++)
627          {
628             SimplePrincipal sp = new SimplePrincipal(roleNames[n]);
629             userRoles.add(sp);
630          }
631       }
632       return userRoles;
633    }
634
635    /**
636     * Create the session principal tomcat will cache to avoid callouts to this
637     * Realm.
638     *
639     * @param realmMapping - the role mapping security manager
640     * @param authPrincipal - the principal used for authentication and stored in
641     * the security manager cache
642     * @param callerPrincipal - the possibly different caller principal
643     * representation of the authenticated principal
644     * @param credential - the credential used for authentication
645     * @return the tomcat session principal wrapper
646     */

647    protected Principal JavaDoc getCachingPrincpal(RealmMapping realmMapping,
648       Principal JavaDoc authPrincipal, Principal JavaDoc callerPrincipal, Object JavaDoc credential,
649       Subject JavaDoc subject)
650    {
651       // Cache the user roles in the principal
652
Set JavaDoc userRoles = realmMapping.getUserRoles(authPrincipal);
653       ArrayList JavaDoc roles = new ArrayList JavaDoc();
654       if (userRoles != null)
655       {
656          Iterator JavaDoc iterator = userRoles.iterator();
657          while (iterator.hasNext())
658          {
659             Principal JavaDoc role = (Principal JavaDoc) iterator.next();
660             roles.add(role.getName());
661          }
662       }
663       JBossGenericPrincipal gp = new JBossGenericPrincipal(this, subject,
664          authPrincipal, callerPrincipal, credential, roles, userRoles);
665       return gp;
666    }
667 }
668
Popular Tags