1 //========================================================================
2 //Copyright 2004-2008 Mort Bay Consulting Pty. Ltd.
3 //------------------------------------------------------------------------
4 //Licensed under the Apache License, Version 2.0 (the "License");
5 //you may not use this file except in compliance with the License.
6 //You may obtain a copy of the License at
7 //http://www.apache.org/licenses/LICENSE-2.0
8 //Unless required by applicable law or agreed to in writing, software
9 //distributed under the License is distributed on an "AS IS" BASIS,
10 //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 //See the License for the specific language governing permissions and
12 //limitations under the License.
13 //========================================================================
14 
15 //========================================================================
16 //$Id:  $
17 //JBoss Jetty Integration
18 //------------------------------------------------------------------------
19 //Licensed under LGPL.
20 //See license terms at http://www.gnu.org/licenses/lgpl.html
21 //========================================================================
22 package org.jboss.jetty.security;
23 
24 import java.io.Serializable;
25 import java.security.Principal;
26 import java.security.cert.X509Certificate;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.Set;
30 import java.util.Stack;
31 
32 import javax.management.MBeanServer;
33 import javax.management.MBeanServerFactory;
34 import javax.management.ObjectName;
35 import javax.naming.Context;
36 import javax.naming.InitialContext;
37 import javax.naming.NamingException;
38 import javax.security.auth.Subject;
39 
40 import org.jboss.jetty.JBossWebAppContext;
41 import org.jboss.logging.Logger;
42 import org.jboss.security.AuthenticationManager;
43 import org.jboss.security.NobodyPrincipal;
44 import org.jboss.security.RealmMapping;
45 import org.jboss.security.RunAsIdentity;
46 import org.jboss.security.SecurityAssociation;
47 import org.jboss.security.SimplePrincipal;
48 import org.jboss.security.SubjectSecurityManager;
49 import org.mortbay.jetty.security.HashSSORealm;
50 import org.mortbay.jetty.Request;
51 import org.mortbay.jetty.Response;
52 import org.mortbay.jetty.security.SSORealm;
53 import org.mortbay.jetty.security.UserRealm;
54 import org.mortbay.jetty.security.Credential;
55 
56 /**
57  * JBossUserRealm
58  * An implementation of UserRealm that integrates with the JBossSX security
59  * manager associted with the web application.
60  *
61  * @author Scott_Stark@displayscape.com
62  * @author Cert Auth by pdawes@users.sf.net
63  * @author SSO Patch by steve.g@byu.edu
64  * @version $Revision: 1.9 $
65  */
66 
67 public class JBossUserRealm implements UserRealm, SSORealm
68 {
69     private final Logger _log;
70     protected final String _realmName;
71     protected final String _subjAttrName;
72     protected SubjectSecurityManager _subjSecMgr = null;
73     protected AuthenticationManager _authMgr = null;
74     private final HashMap _users = new HashMap();
75     protected RealmMapping _realmMapping = null;
76     protected JBossWebAppContext _jbossWebAppContext = null;
77     /*
78      * Since there is a seperate instance of JBossUserRealm per web-app
79      * regardless of whether the realm-name is the same, this creates an
80      * instance of HashSSORealm shared between all JBossUserRealms that have the
81      * same realm-name.
82      */
83     private final static HashMap _sharedHashSSORealms = new HashMap();
84     private String _ssoRealmName = null;
85     private HashSSORealm _ssoRealm = null;
86 
87 
88 
89     /**
90      * JBossUserPrincipal
91      *
92      *
93      */
94     static class JBossUserPrincipal implements Principal, Serializable
95     {
96         protected transient Logger _logRef;
97         protected transient JBossUserRealm _realm;
98         protected Principal _principal;
99         private String _password;
100         private Stack _roleStack= new Stack();;
101 
JBossUserPrincipal()102         JBossUserPrincipal() {}
103 
JBossUserPrincipal(String name, Logger log)104         JBossUserPrincipal(String name, Logger log)
105         {
106             _principal = new SimplePrincipal(name);
107             this._logRef = log;
108 
109             if (log.isDebugEnabled())
110                 log.debug("created JBossUserRealm::JBossUserPrincipal: " + name);
111         }
112 
associateWithRealm(JBossUserRealm realm)113         void associateWithRealm(JBossUserRealm realm)
114         {
115             this._realm = realm;
116         }
117 
isAuthenticated(String password)118         private boolean isAuthenticated(String password)
119         {
120             boolean authenticated = false;
121 
122             if (password == null) password = "";
123             char[] passwordChars = password.toCharArray();
124 
125             if (_logRef.isDebugEnabled())
126                 _logRef.debug("authenticating: Name:" + _principal + " Password:****"/* +password */);
127 
128             Subject subjectCopy = new Subject();
129 
130             if (_realm._subjSecMgr != null && _realm._subjSecMgr.isValid(this._principal, passwordChars, subjectCopy))
131             {
132                 if (_logRef.isDebugEnabled())
133                     _logRef.debug("authenticated: " + _principal);
134 
135                 SecurityAssociation.setPrincipal(_principal);
136                 SecurityAssociation.setCredential(passwordChars);
137                 SecurityAssociation.setSubject(subjectCopy);
138                 authenticated = true;
139             }
140             else
141             {
142                 _logRef.warn("authentication failure: " + _principal);
143             }
144 
145             return authenticated;
146         }
147 
equals(Object o)148         public boolean equals(Object o)
149         {
150             if (o == this) return true;
151 
152             if (o == null) return false;
153 
154             if (getClass() != o.getClass()) return false;
155 
156             String myName = this.getName();
157             String yourName = ((JBossUserPrincipal) o).getName();
158 
159             if (myName == null && yourName == null) return true;
160 
161             if (myName != null && myName.equals(yourName)) return true;
162 
163             return false;
164         }
165 
166 
getName()167         public String getName()
168         {
169             return _realm._realmMapping.getPrincipal(_principal).getName();
170         }
171 
172 
authenticate(String password, Request request)173         public boolean authenticate(String password, Request request)
174         {
175             _password = password;
176             boolean authenticated = false;
177             authenticated = isAuthenticated(_password);
178 
179             if (authenticated && _realm._subjSecMgr != null)
180             {
181                 Subject subject = _realm._subjSecMgr.getActiveSubject();
182                 request.setAttribute(_realm._subjAttrName, subject);
183             }
184 
185             return authenticated;
186         }
187 
isAuthenticated()188         public boolean isAuthenticated()
189         {
190             return isAuthenticated(_password);
191         }
192 
193 
isUserInRole(String role)194         public boolean isUserInRole(String role)
195         {
196             boolean isUserInRole = false;
197 
198             if (!_roleStack.isEmpty() && _roleStack.peek().equals(role))
199                 return true;
200 
201             Set requiredRoles = Collections.singleton(new SimplePrincipal(role));
202             if (_realm._realmMapping != null
203                && _realm._realmMapping.doesUserHaveRole(this._principal,requiredRoles))
204             {
205                 if (_logRef.isDebugEnabled())
206                     _logRef.debug("JBossUserPrincipal: " + _principal + " is in Role: " + role);
207 
208                 isUserInRole = true;
209             }
210             else
211             {
212                 if (_logRef.isDebugEnabled())
213                     _logRef.debug("JBossUserPrincipal: " + _principal + " is NOT in Role: " + role);
214             }
215 
216             return isUserInRole;
217         }
218 
toString()219         public String toString()
220         {
221             return getName();
222         }
223 
push(String roleName)224         public void push (String roleName)
225         {
226             _roleStack.push(roleName);
227         }
228 
pop()229         public void pop ()
230         {
231             _roleStack.pop();
232         }
233     }
234 
235     /**
236      * JBossNobodyUserPrincipal
237      * Represents the default user.
238      */
239     static class JBossNobodyUserPrincipal extends JBossUserPrincipal
240     {
JBossNobodyUserPrincipal(Logger log)241         public JBossNobodyUserPrincipal(Logger log)
242         {
243             _principal = new NobodyPrincipal();
244             this._logRef = log;
245 
246             if (log.isDebugEnabled())
247                 log.debug("created JBossUserRealm::JBossNobodyUserPrincipal");
248         }
249 
isAuthenticated()250         public boolean isAuthenticated()
251         {
252             return true;
253         }
254 
authenticate(String password, Request request)255         public boolean authenticate(String password, Request request)
256         {
257             return true;
258         }
259 
260     }
261 
262     /**
263      * JBossCertificatePrincipal
264      * Represents a user which has been authenticated elsewhere
265      * (e.g. at the fronting server), and thus doesnt have credentials
266      *
267      */
268     static class JBossCertificatePrincipal extends JBossUserPrincipal
269     {
270         private X509Certificate[] _certs;
271 
JBossCertificatePrincipal(String name, Logger log, X509Certificate[] certs)272         JBossCertificatePrincipal(String name, Logger log, X509Certificate[] certs)
273         {
274             super(name, log);
275             _certs = certs;
276             if (_logRef.isDebugEnabled())
277                 _logRef.debug("created JBossUserRealm::JBossCertificatePrincipal: "+ name);
278         }
279 
isAuthenticated()280         public boolean isAuthenticated()
281         {
282             // TODO I'm dubious if this is correct???
283             _logRef.debug("JBossUserRealm::isAuthenticated called");
284             return true;
285         }
286 
authenticate()287         public boolean authenticate()
288         {
289             boolean authenticated = false;
290 
291             if (_logRef.isDebugEnabled())
292                 _logRef.debug("authenticating: Name:" + _principal);
293 
294             // Authenticate using the cert as the credential
295             Subject subjectCopy = new Subject();
296             if (_realm._subjSecMgr != null && _realm._subjSecMgr.isValid(_principal, _certs, subjectCopy))
297             {
298                 if (_logRef.isDebugEnabled())
299                     _logRef.debug("authenticated: " + _principal);
300 
301                 SecurityAssociation.setPrincipal(_principal);
302                 SecurityAssociation.setCredential(_certs);
303                 SecurityAssociation.setSubject(subjectCopy);
304                 authenticated = true;
305             }
306             else
307             {
308                 _logRef.warn("authentication failure: " + _principal);
309             }
310 
311             return authenticated;
312         }
313     }
314 
JBossUserRealm(String realmName, String subjAttrName)315     public JBossUserRealm(String realmName, String subjAttrName)
316     {
317         _realmName = realmName;
318         _log = Logger.getLogger(JBossUserRealm.class.getName() + "#"+ _realmName);
319         _subjAttrName = subjAttrName;
320 
321         //always add a default user?
322         JBossUserPrincipal nobody = new JBossNobodyUserPrincipal(_log);
323         nobody.associateWithRealm(this);
324         _users.put("nobody", nobody);
325     }
326 
init()327     public void init()
328     {
329         _log.debug("initialising realm "+_realmName);
330         try
331         {
332             InitialContext iniCtx = new InitialContext();
333             Context securityCtx = (Context) iniCtx.lookup("java:comp/env/security");
334             _authMgr = (AuthenticationManager) securityCtx.lookup("securityMgr");
335             _realmMapping = (RealmMapping) securityCtx.lookup("realmMapping");
336             iniCtx = null;
337 
338             if (_authMgr instanceof SubjectSecurityManager)
339                 _subjSecMgr = (SubjectSecurityManager) _authMgr;
340         }
341         catch (NamingException e)
342         {
343             _log.error("java:comp/env/security does not appear to be correctly set up", e);
344         }
345         _log.debug("...initialised");
346     }
347 
348     // this is going to cause contention - TODO
ensureUser(String userName)349     private synchronized JBossUserPrincipal ensureUser(String userName)
350     {
351         JBossUserPrincipal user = (JBossUserPrincipal) _users.get(userName);
352 
353         if (user == null)
354         {
355             user = new JBossUserPrincipal(userName, _log);
356             user.associateWithRealm(this);
357             _users.put(userName, user);
358         }
359 
360         return user;
361     }
362 
getPrincipal(String username)363     public Principal getPrincipal(String username)
364     {
365         return (Principal) _users.get(username);
366     }
367 
368     /**
369      * @deprecated
370      */
getUserPrincipal(String username)371     public Principal getUserPrincipal(String username)
372     {
373         return (Principal) _users.get(username);
374     }
375 
authenticate(String userName, Object credential, Request request)376     public Principal authenticate(String userName, Object credential,
377             Request request)
378     {
379         if (_log.isDebugEnabled())
380             _log.debug("JBossUserPrincipal: " + userName);
381 
382         // until we get DigestAuthentication sorted JBoss side...
383         JBossUserPrincipal user = null;
384 
385         if (credential instanceof java.lang.String) // password
386         {
387             user = ensureUser(userName);
388             if (!user.authenticate((String) credential, request))
389             {
390                 user = null;
391             }
392         }
393         else if (credential instanceof X509Certificate[]) // certificate
394         {
395             X509Certificate[] certs = (X509Certificate[]) credential;
396             user = this.authenticateFromCertificates(certs);
397         }
398 
399         if (user != null)
400         {
401             request.setAuthType(javax.servlet.http.HttpServletRequest.CLIENT_CERT_AUTH);
402             request.setUserPrincipal(user);
403         }
404 
405         return user;
406     }
407 
reauthenticate(Principal user)408     public boolean reauthenticate(Principal user)
409     {
410         return ((JBossUserPrincipal) user).isAuthenticated();
411     }
412 
413     /**
414      * @deprecated Use reauthenticate
415      */
isAuthenticated(Principal user)416     public boolean isAuthenticated(Principal user)
417     {
418         return ((JBossUserPrincipal) user).isAuthenticated();
419     }
420 
isUserInRole(Principal user, String role)421     public boolean isUserInRole(Principal user, String role)
422     {
423         return ((JBossUserPrincipal) user).isUserInRole(role);
424     }
425 
authenticateFromCertificates( X509Certificate[] certs)426     public JBossUserPrincipal authenticateFromCertificates(
427             X509Certificate[] certs)
428     {
429         JBossCertificatePrincipal user = (JBossCertificatePrincipal) _users
430                 .get(certs[0]);
431 
432         if (user == null)
433         {
434             user = new JBossCertificatePrincipal(getFilterFromCertificate(certs[0]), _log, certs);
435             user.associateWithRealm(this);
436             _users.put(certs[0], user);
437         }
438 
439         if (user.authenticate())
440         {
441             _log.debug("authenticateFromCertificates - authenticated");
442             return user;
443         }
444 
445         _log.debug("authenticateFromCertificates - returning NULL");
446         return null;
447     }
448 
449     /**
450      * Takes an X509Certificate object and extracts the certificate's serial
451      * number and issuer in order to construct a unique string representing that
452      * certificate.
453      *
454      * @param cert the user's certificate.
455      * @return an LDAP filter for retrieving the user's entry.
456      */
getFilterFromCertificate(X509Certificate cert)457     private String getFilterFromCertificate(X509Certificate cert)
458     {
459         StringBuffer buff = new StringBuffer();
460         String serialNumber = cert.getSerialNumber().toString(16).toUpperCase();
461 
462         if (serialNumber.length() % 2 != 0) buff.append("0");
463 
464         buff.append(serialNumber);
465         buff.append(" ");
466         buff.append(cert.getIssuerDN().toString());
467         String filter = buff.toString();
468         return filter;
469     }
470 
disassociate(Principal user)471     public void disassociate(Principal user)
472     {
473         SecurityAssociation.clear();
474     }
475 
pushRole(Principal user, String role)476     public Principal pushRole(Principal user, String role)
477     {
478         RunAsIdentity runAs = new RunAsIdentity(role, (user==null?null:user.getName()));
479         if (user==null)
480             user = (JBossUserPrincipal)_users.get("nobody");
481 
482         //set up security for Jetty
483         ((JBossUserPrincipal)user).push(role);
484         //set up security for calls to jboss ejbs
485         SecurityAssociation.pushRunAsIdentity(runAs);
486 
487         return user;
488     }
489 
popRole(Principal user)490     public Principal popRole(Principal user)
491     {
492         ((JBossUserPrincipal)user).pop();
493         //clear a run-as role set for jboss ejb calls
494         SecurityAssociation.popRunAsIdentity();
495         return user;
496     }
497 
logout(Principal user)498     public void logout(Principal user)
499     {
500         // yukky hack to try and force JBoss to actually
501         // flush the user from the jaas security manager's cache therefore
502         // forcing logincontext.logout() to be called
503         try
504         {
505             Principal pUser = user;
506             if (user instanceof JBossUserPrincipal)
507                 pUser = ((JBossUserPrincipal) user)._principal;
508 
509             java.util.ArrayList servers = MBeanServerFactory.findMBeanServer(null);
510             if (servers.size() != 1)
511                 _log.warn("More than one MBeanServer found, choosing first");
512             MBeanServer server = (MBeanServer) servers.get(0);
513 
514             server.invoke(new ObjectName("jboss.security:service=JaasSecurityManager"),
515                                          "flushAuthenticationCache",
516                                          new Object[] { getName(), pUser },
517                                          new String[] {"java.lang.String", "java.security.Principal" });
518         }
519         catch (Exception e)
520         {
521             _log.error(e);
522         }
523         catch (Error err)
524         {
525             _log.error(err);
526         }
527     }
528 
529     /**
530      * @param name The name of a Single Sign On realm. Realms that share a sso
531      *            realm will share authentication for users. Null if no SSO
532      *            realm.
533      */
setSSORealmName(String name)534     public void setSSORealmName(String name)
535     {
536         _ssoRealmName = name;
537         _ssoRealm = null;
538     }
539 
540     /**
541      * @return The name of a Single Sign On realm. Realms that share a sso realm
542      *         will share authentication for users. Null if no SSO realm.
543      */
getSSORealmName()544     public String getSSORealmName()
545     {
546         return _ssoRealmName;
547     }
548 
getSingleSignOn(Request request, Response response)549     public Credential getSingleSignOn(Request request, Response response)
550     {
551         if (!isSSORealm()) return null;
552         Credential singleSignOnCredential = _ssoRealm.getSingleSignOn(request,
553                 response);
554         if (_log.isDebugEnabled())
555             _log.debug("getSingleSignOn principal="
556                     + request.getUserPrincipal() + " credential="
557                     + singleSignOnCredential);
558         return singleSignOnCredential;
559 
560     }
561 
setSingleSignOn(Request request, Response response, Principal principal, Credential credential)562     public void setSingleSignOn(Request request, Response response,
563             Principal principal, Credential credential)
564     {
565         if (!isSSORealm()) return;
566         if (_log.isDebugEnabled())
567             _log.debug("setSingleSignOn called. principal=" + principal
568                     + " credential=" + credential);
569         _ssoRealm.setSingleSignOn(request, response, principal, credential);
570     }
571 
clearSingleSignOn(String username)572     public void clearSingleSignOn(String username)
573     {
574         if (!isSSORealm()) return;
575 
576         if (_log.isDebugEnabled())
577             _log.debug("clearSingleSignOn called. username=" + username);
578         _ssoRealm.clearSingleSignOn(username);
579         SecurityAssociation.setPrincipal(null);
580         SecurityAssociation.setCredential(null);
581     }
582 
isSSORealm()583     private boolean isSSORealm()
584     {
585         if (_ssoRealm == null && _ssoRealmName != null)
586         {
587             synchronized (_sharedHashSSORealms)
588             {
589                 _ssoRealm = (HashSSORealm) _sharedHashSSORealms
590                         .get(_ssoRealmName);
591                 if (_ssoRealm == null)
592                 {
593                     _log.debug("created SSORealm for " + _ssoRealmName);
594                     _ssoRealm = new HashSSORealm();
595                     _sharedHashSSORealms.put(_ssoRealmName, _ssoRealm);
596                 }
597             }
598         }
599         return _ssoRealm != null;
600     }
601 
getName()602     public String getName()
603     {
604         return _realmName;
605     }
606 }
607