001: //========================================================================
002: //$Id: $
003: //JBoss Jetty Integration
004: //------------------------------------------------------------------------
005: //Licensed under LGPL.
006: //See license terms at http://www.gnu.org/licenses/lgpl.html
007: //========================================================================
008: package org.jboss.jetty.security;
009:
010: import java.io.Serializable;
011: import java.security.Principal;
012: import java.security.cert.X509Certificate;
013: import java.util.Collections;
014: import java.util.HashMap;
015: import java.util.Set;
016: import java.util.Stack;
017:
018: import javax.management.MBeanServer;
019: import javax.management.MBeanServerFactory;
020: import javax.management.ObjectName;
021: import javax.naming.Context;
022: import javax.naming.InitialContext;
023: import javax.naming.NamingException;
024: import javax.security.auth.Subject;
025:
026: import org.jboss.jetty.JBossWebAppContext;
027: import org.jboss.logging.Logger;
028: import org.jboss.security.AuthenticationManager;
029: import org.jboss.security.NobodyPrincipal;
030: import org.jboss.security.RealmMapping;
031: import org.jboss.security.RunAsIdentity;
032: import org.jboss.security.SecurityAssociation;
033: import org.jboss.security.SimplePrincipal;
034: import org.jboss.security.SubjectSecurityManager;
035: import org.mortbay.jetty.security.HashSSORealm;
036: import org.mortbay.jetty.Request;
037: import org.mortbay.jetty.Response;
038: import org.mortbay.jetty.security.SSORealm;
039: import org.mortbay.jetty.security.UserRealm;
040: import org.mortbay.jetty.security.Credential;
041:
042: /**
043: * JBossUserRealm
044: * An implementation of UserRealm that integrates with the JBossSX security
045: * manager associted with the web application.
046: *
047: * @author Scott_Stark@displayscape.com
048: * @author Cert Auth by pdawes@users.sf.net
049: * @author SSO Patch by steve.g@byu.edu
050: * @version $Revision: 1.9 $
051: */
052:
053: public class JBossUserRealm implements UserRealm, SSORealm {
054: private final Logger _log;
055: protected final String _realmName;
056: protected final String _subjAttrName;
057: protected SubjectSecurityManager _subjSecMgr = null;
058: protected AuthenticationManager _authMgr = null;
059: private final HashMap _users = new HashMap();
060: protected RealmMapping _realmMapping = null;
061: protected JBossWebAppContext _jbossWebAppContext = null;
062: /*
063: * Since there is a seperate instance of JBossUserRealm per web-app
064: * regardless of whether the realm-name is the same, this creates an
065: * instance of HashSSORealm shared between all JBossUserRealms that have the
066: * same realm-name.
067: */
068: private final static HashMap _sharedHashSSORealms = new HashMap();
069: private String _ssoRealmName = null;
070: private HashSSORealm _ssoRealm = null;
071:
072: /**
073: * JBossUserPrincipal
074: *
075: *
076: */
077: static class JBossUserPrincipal implements Principal, Serializable {
078: protected transient Logger _logRef;
079: protected transient JBossUserRealm _realm;
080: protected Principal _principal;
081: private String _password;
082: private Stack _roleStack = new Stack();;
083:
084: JBossUserPrincipal() {
085: }
086:
087: JBossUserPrincipal(String name, Logger log) {
088: _principal = new SimplePrincipal(name);
089: this ._logRef = log;
090:
091: if (log.isDebugEnabled())
092: log
093: .debug("created JBossUserRealm::JBossUserPrincipal: "
094: + name);
095: }
096:
097: void associateWithRealm(JBossUserRealm realm) {
098: this ._realm = realm;
099: }
100:
101: private boolean isAuthenticated(String password) {
102: boolean authenticated = false;
103:
104: if (password == null)
105: password = "";
106: char[] passwordChars = password.toCharArray();
107:
108: if (_logRef.isDebugEnabled())
109: _logRef.debug("authenticating: Name:" + _principal
110: + " Password:****"/* +password */);
111:
112: Subject subjectCopy = new Subject();
113:
114: if (_realm._subjSecMgr != null
115: && _realm._subjSecMgr.isValid(this ._principal,
116: passwordChars, subjectCopy)) {
117: if (_logRef.isDebugEnabled())
118: _logRef.debug("authenticated: " + _principal);
119:
120: SecurityAssociation.setPrincipal(_principal);
121: SecurityAssociation.setCredential(passwordChars);
122: SecurityAssociation.setSubject(subjectCopy);
123: authenticated = true;
124: } else {
125: _logRef.warn("authentication failure: " + _principal);
126: }
127:
128: return authenticated;
129: }
130:
131: public boolean equals(Object o) {
132: if (o == this )
133: return true;
134:
135: if (o == null)
136: return false;
137:
138: if (getClass() != o.getClass())
139: return false;
140:
141: String myName = this .getName();
142: String yourName = ((JBossUserPrincipal) o).getName();
143:
144: if (myName == null && yourName == null)
145: return true;
146:
147: if (myName != null && myName.equals(yourName))
148: return true;
149:
150: return false;
151: }
152:
153: public String getName() {
154: return _realm._realmMapping.getPrincipal(_principal)
155: .getName();
156: }
157:
158: public boolean authenticate(String password, Request request) {
159: _password = password;
160: boolean authenticated = false;
161: authenticated = isAuthenticated(_password);
162:
163: if (authenticated && _realm._subjSecMgr != null) {
164: Subject subject = _realm._subjSecMgr.getActiveSubject();
165: request.setAttribute(_realm._subjAttrName, subject);
166: }
167:
168: return authenticated;
169: }
170:
171: public boolean isAuthenticated() {
172: return isAuthenticated(_password);
173: }
174:
175: public boolean isUserInRole(String role) {
176: boolean isUserInRole = false;
177:
178: if (!_roleStack.isEmpty() && _roleStack.peek().equals(role))
179: return true;
180:
181: Set requiredRoles = Collections
182: .singleton(new SimplePrincipal(role));
183: if (_realm._realmMapping != null
184: && _realm._realmMapping.doesUserHaveRole(
185: this ._principal, requiredRoles)) {
186: if (_logRef.isDebugEnabled())
187: _logRef.debug("JBossUserPrincipal: " + _principal
188: + " is in Role: " + role);
189:
190: isUserInRole = true;
191: } else {
192: if (_logRef.isDebugEnabled())
193: _logRef.debug("JBossUserPrincipal: " + _principal
194: + " is NOT in Role: " + role);
195: }
196:
197: return isUserInRole;
198: }
199:
200: public String toString() {
201: return getName();
202: }
203:
204: public void push(String roleName) {
205: _roleStack.push(roleName);
206: }
207:
208: public void pop() {
209: _roleStack.pop();
210: }
211: }
212:
213: /**
214: * JBossNobodyUserPrincipal
215: * Represents the default user.
216: */
217: static class JBossNobodyUserPrincipal extends JBossUserPrincipal {
218: public JBossNobodyUserPrincipal(Logger log) {
219: _principal = new NobodyPrincipal();
220: this ._logRef = log;
221:
222: if (log.isDebugEnabled())
223: log
224: .debug("created JBossUserRealm::JBossNobodyUserPrincipal");
225: }
226:
227: public boolean isAuthenticated() {
228: return true;
229: }
230:
231: public boolean authenticate(String password, Request request) {
232: return true;
233: }
234:
235: }
236:
237: /**
238: * JBossCertificatePrincipal
239: * Represents a user which has been authenticated elsewhere
240: * (e.g. at the fronting server), and thus doesnt have credentials
241: *
242: */
243: static class JBossCertificatePrincipal extends JBossUserPrincipal {
244: private X509Certificate[] _certs;
245:
246: JBossCertificatePrincipal(String name, Logger log,
247: X509Certificate[] certs) {
248: super (name, log);
249: _certs = certs;
250: if (_logRef.isDebugEnabled())
251: _logRef
252: .debug("created JBossUserRealm::JBossCertificatePrincipal: "
253: + name);
254: }
255:
256: public boolean isAuthenticated() {
257: // TODO I'm dubious if this is correct???
258: _logRef.debug("JBossUserRealm::isAuthenticated called");
259: return true;
260: }
261:
262: public boolean authenticate() {
263: boolean authenticated = false;
264:
265: if (_logRef.isDebugEnabled())
266: _logRef.debug("authenticating: Name:" + _principal);
267:
268: // Authenticate using the cert as the credential
269: Subject subjectCopy = new Subject();
270: if (_realm._subjSecMgr != null
271: && _realm._subjSecMgr.isValid(_principal, _certs,
272: subjectCopy)) {
273: if (_logRef.isDebugEnabled())
274: _logRef.debug("authenticated: " + _principal);
275:
276: SecurityAssociation.setPrincipal(_principal);
277: SecurityAssociation.setCredential(_certs);
278: SecurityAssociation.setSubject(subjectCopy);
279: authenticated = true;
280: } else {
281: _logRef.warn("authentication failure: " + _principal);
282: }
283:
284: return authenticated;
285: }
286: }
287:
288: public JBossUserRealm(String realmName, String subjAttrName) {
289: _realmName = realmName;
290: _log = Logger.getLogger(JBossUserRealm.class.getName() + "#"
291: + _realmName);
292: _subjAttrName = subjAttrName;
293:
294: //always add a default user?
295: JBossUserPrincipal nobody = new JBossNobodyUserPrincipal(_log);
296: nobody.associateWithRealm(this );
297: _users.put("nobody", nobody);
298: }
299:
300: public void init() {
301: _log.debug("initialising realm " + _realmName);
302: try {
303: InitialContext iniCtx = new InitialContext();
304: Context securityCtx = (Context) iniCtx
305: .lookup("java:comp/env/security");
306: _authMgr = (AuthenticationManager) securityCtx
307: .lookup("securityMgr");
308: _realmMapping = (RealmMapping) securityCtx
309: .lookup("realmMapping");
310: iniCtx = null;
311:
312: if (_authMgr instanceof SubjectSecurityManager)
313: _subjSecMgr = (SubjectSecurityManager) _authMgr;
314: } catch (NamingException e) {
315: _log
316: .error(
317: "java:comp/env/security does not appear to be correctly set up",
318: e);
319: }
320: _log.debug("...initialised");
321: }
322:
323: // this is going to cause contention - TODO
324: private synchronized JBossUserPrincipal ensureUser(String userName) {
325: JBossUserPrincipal user = (JBossUserPrincipal) _users
326: .get(userName);
327:
328: if (user == null) {
329: user = new JBossUserPrincipal(userName, _log);
330: user.associateWithRealm(this );
331: _users.put(userName, user);
332: }
333:
334: return user;
335: }
336:
337: public Principal getPrincipal(String username) {
338: return (Principal) _users.get(username);
339: }
340:
341: /**
342: * @deprecated
343: */
344: public Principal getUserPrincipal(String username) {
345: return (Principal) _users.get(username);
346: }
347:
348: public Principal authenticate(String userName, Object credential,
349: Request request) {
350: if (_log.isDebugEnabled())
351: _log.debug("JBossUserPrincipal: " + userName);
352:
353: // until we get DigestAuthentication sorted JBoss side...
354: JBossUserPrincipal user = null;
355:
356: if (credential instanceof java.lang.String) // password
357: {
358: user = ensureUser(userName);
359: if (!user.authenticate((String) credential, request)) {
360: user = null;
361: }
362: } else if (credential instanceof X509Certificate[]) // certificate
363: {
364: X509Certificate[] certs = (X509Certificate[]) credential;
365: user = this .authenticateFromCertificates(certs);
366: }
367:
368: if (user != null) {
369: request
370: .setAuthType(javax.servlet.http.HttpServletRequest.CLIENT_CERT_AUTH);
371: request.setUserPrincipal(user);
372: }
373:
374: return user;
375: }
376:
377: public boolean reauthenticate(Principal user) {
378: return ((JBossUserPrincipal) user).isAuthenticated();
379: }
380:
381: /**
382: * @deprecated Use reauthenticate
383: */
384: public boolean isAuthenticated(Principal user) {
385: return ((JBossUserPrincipal) user).isAuthenticated();
386: }
387:
388: public boolean isUserInRole(Principal user, String role) {
389: return ((JBossUserPrincipal) user).isUserInRole(role);
390: }
391:
392: public JBossUserPrincipal authenticateFromCertificates(
393: X509Certificate[] certs) {
394: JBossCertificatePrincipal user = (JBossCertificatePrincipal) _users
395: .get(certs[0]);
396:
397: if (user == null) {
398: user = new JBossCertificatePrincipal(
399: getFilterFromCertificate(certs[0]), _log, certs);
400: user.associateWithRealm(this );
401: _users.put(certs[0], user);
402: }
403:
404: if (user.authenticate()) {
405: _log.debug("authenticateFromCertificates - authenticated");
406: return user;
407: }
408:
409: _log.debug("authenticateFromCertificates - returning NULL");
410: return null;
411: }
412:
413: /**
414: * Takes an X509Certificate object and extracts the certificate's serial
415: * number and issuer in order to construct a unique string representing that
416: * certificate.
417: *
418: * @param cert the user's certificate.
419: * @return an LDAP filter for retrieving the user's entry.
420: */
421: private String getFilterFromCertificate(X509Certificate cert) {
422: StringBuffer buff = new StringBuffer();
423: String serialNumber = cert.getSerialNumber().toString(16)
424: .toUpperCase();
425:
426: if (serialNumber.length() % 2 != 0)
427: buff.append("0");
428:
429: buff.append(serialNumber);
430: buff.append(" ");
431: buff.append(cert.getIssuerDN().toString());
432: String filter = buff.toString();
433: return filter;
434: }
435:
436: public void disassociate(Principal user) {
437: SecurityAssociation.clear();
438: }
439:
440: public Principal pushRole(Principal user, String role) {
441: RunAsIdentity runAs = new RunAsIdentity(role,
442: (user == null ? null : user.getName()));
443: if (user == null)
444: user = (JBossUserPrincipal) _users.get("nobody");
445:
446: //set up security for Jetty
447: ((JBossUserPrincipal) user).push(role);
448: //set up security for calls to jboss ejbs
449: SecurityAssociation.pushRunAsIdentity(runAs);
450:
451: return user;
452: }
453:
454: public Principal popRole(Principal user) {
455: ((JBossUserPrincipal) user).pop();
456: //clear a run-as role set for jboss ejb calls
457: SecurityAssociation.popRunAsIdentity();
458: return user;
459: }
460:
461: public void logout(Principal user) {
462: // yukky hack to try and force JBoss to actually
463: // flush the user from the jaas security manager's cache therefore
464: // forcing logincontext.logout() to be called
465: try {
466: Principal pUser = user;
467: if (user instanceof JBossUserPrincipal)
468: pUser = ((JBossUserPrincipal) user)._principal;
469:
470: java.util.ArrayList servers = MBeanServerFactory
471: .findMBeanServer(null);
472: if (servers.size() != 1)
473: _log
474: .warn("More than one MBeanServer found, choosing first");
475: MBeanServer server = (MBeanServer) servers.get(0);
476:
477: server.invoke(new ObjectName(
478: "jboss.security:service=JaasSecurityManager"),
479: "flushAuthenticationCache", new Object[] {
480: getName(), pUser }, new String[] {
481: "java.lang.String",
482: "java.security.Principal" });
483: } catch (Exception e) {
484: _log.error(e);
485: } catch (Error err) {
486: _log.error(err);
487: }
488: }
489:
490: /**
491: * @param name The name of a Single Sign On realm. Realms that share a sso
492: * realm will share authentication for users. Null if no SSO
493: * realm.
494: */
495: public void setSSORealmName(String name) {
496: _ssoRealmName = name;
497: _ssoRealm = null;
498: }
499:
500: /**
501: * @return The name of a Single Sign On realm. Realms that share a sso realm
502: * will share authentication for users. Null if no SSO realm.
503: */
504: public String getSSORealmName() {
505: return _ssoRealmName;
506: }
507:
508: public Credential getSingleSignOn(Request request, Response response) {
509: if (!isSSORealm())
510: return null;
511: Credential singleSignOnCredential = _ssoRealm.getSingleSignOn(
512: request, response);
513: if (_log.isDebugEnabled())
514: _log.debug("getSingleSignOn principal="
515: + request.getUserPrincipal() + " credential="
516: + singleSignOnCredential);
517: return singleSignOnCredential;
518:
519: }
520:
521: public void setSingleSignOn(Request request, Response response,
522: Principal principal, Credential credential) {
523: if (!isSSORealm())
524: return;
525: if (_log.isDebugEnabled())
526: _log.debug("setSingleSignOn called. principal=" + principal
527: + " credential=" + credential);
528: _ssoRealm.setSingleSignOn(request, response, principal,
529: credential);
530: }
531:
532: public void clearSingleSignOn(String username) {
533: if (!isSSORealm())
534: return;
535:
536: if (_log.isDebugEnabled())
537: _log
538: .debug("clearSingleSignOn called. username="
539: + username);
540: _ssoRealm.clearSingleSignOn(username);
541: SecurityAssociation.setPrincipal(null);
542: SecurityAssociation.setCredential(null);
543: }
544:
545: private boolean isSSORealm() {
546: if (_ssoRealm == null && _ssoRealmName != null) {
547: synchronized (_sharedHashSSORealms) {
548: _ssoRealm = (HashSSORealm) _sharedHashSSORealms
549: .get(_ssoRealmName);
550: if (_ssoRealm == null) {
551: _log.debug("created SSORealm for " + _ssoRealmName);
552: _ssoRealm = new HashSSORealm();
553: _sharedHashSSORealms.put(_ssoRealmName, _ssoRealm);
554: }
555: }
556: }
557: return _ssoRealm != null;
558: }
559:
560: public String getName() {
561: return _realmName;
562: }
563: }
|