001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.mq.security;
023:
024: import java.security.Principal;
025: import java.security.acl.Group;
026: import java.util.Enumeration;
027: import java.util.HashMap;
028: import java.util.Iterator;
029: import java.util.Set;
030:
031: import javax.jms.JMSException;
032: import javax.jms.JMSSecurityException;
033: import javax.management.MBeanServer;
034: import javax.management.MalformedObjectNameException;
035: import javax.management.ObjectName;
036: import javax.naming.Context;
037: import javax.naming.InitialContext;
038: import javax.naming.NamingException;
039: import javax.security.auth.Subject;
040:
041: import org.jboss.mq.ConnectionToken;
042: import org.jboss.mq.server.JMSServerInterceptor;
043: import org.jboss.mq.server.jmx.InterceptorMBeanSupport;
044: import org.jboss.security.SimplePrincipal;
045: import org.jboss.security.SubjectSecurityManager;
046: import org.w3c.dom.Element;
047:
048: /**
049: * A JAAS based security manager for JBossMQ.
050: *
051: * @author Peter Antman
052: * @author Scott.Stark@jboss.org
053: * @version $Revision: 57198 $
054: */
055: public class SecurityManager extends InterceptorMBeanSupport implements
056: SecurityManagerMBean {
057: /**
058: * Cached info on subject, to speed lookups.
059: */
060: class SubjectInfo {
061: Subject subject;
062: Principal principal;
063: Group roles;
064:
065: public String toString() {
066: return "SubjectInfo {subject=" + subject + ";principal="
067: + principal + ";roles=" + roles.toString();
068: }
069: }
070:
071: private ObjectName name;
072: Context securityCtx;
073: HashMap authCache = new HashMap(32);
074: HashMap securityConf = new HashMap(32);
075: ServerSecurityInterceptor interceptor;
076: SubjectSecurityManager sec;
077: SessionIDGenerator idGenerator;
078: Element defaultSecurityConfig;
079: String securityDomain;
080:
081: protected ObjectName getObjectName(MBeanServer server,
082: ObjectName name) throws MalformedObjectNameException {
083:
084: this .name = name == null ? OBJECT_NAME : name;
085:
086: return this .name;
087: }
088:
089: public JMSServerInterceptor getInvoker() {
090: return interceptor;
091: }
092:
093: public Element getDefaultSecurityConfig() {
094: return defaultSecurityConfig;
095: }
096:
097: public void setDefaultSecurityConfig(Element conf) throws Exception {
098: defaultSecurityConfig = conf;
099: // Force a parse
100: new SecurityMetadata(conf);
101: }
102:
103: public String getSecurityDomain() {
104: return securityDomain;
105: }
106:
107: public void setSecurityDomain(String securityDomain) {
108: this .securityDomain = securityDomain;
109: }
110:
111: // DEBUGGING METHOD: DANGEROUS
112: public String printAuthCache() {
113: return authCache.toString();
114: }
115:
116: public void addDestination(String destName, Element conf)
117: throws Exception {
118: SecurityMetadata m = new SecurityMetadata(conf);
119: securityConf.put(destName, m);
120: }
121:
122: public void addDestination(String destName, String conf)
123: throws Exception {
124: SecurityMetadata m = new SecurityMetadata(conf);
125: securityConf.put(destName, m);
126: }
127:
128: public void removeDestination(String destName) throws Exception {
129: securityConf.remove(destName);
130: }
131:
132: public SecurityMetadata getSecurityMetadata(String destName) {
133: SecurityMetadata m = (SecurityMetadata) securityConf
134: .get(destName);
135: if (m == null) {
136: // No SecurityManager was configured for the dest,
137: // Apply the default
138: if (defaultSecurityConfig != null) {
139: log.debug("No SecurityMetadadata was available for "
140: + destName + " using default security config");
141: try {
142: m = new SecurityMetadata(defaultSecurityConfig);
143: } catch (Exception e) {
144: log.warn(
145: "Unable to apply default security for destName, using guest "
146: + destName, e);
147: m = new SecurityMetadata();
148: }
149: } else {
150: // default to guest
151: log.warn("No SecurityMetadadata was available for "
152: + destName + " adding guest");
153: m = new SecurityMetadata();
154: }
155: securityConf.put(destName, m);
156: }
157: return m;
158: }
159:
160: public void startService() throws Exception {
161: // Get the JBoss security manager from JNDI
162: InitialContext iniCtx = new InitialContext();
163: try {
164: sec = (SubjectSecurityManager) iniCtx
165: .lookup(securityDomain);
166: } catch (NamingException e) {
167: // Apparently there is no security context, try adding java:/jaas
168: log.debug("Failed to lookup securityDomain="
169: + securityDomain, e);
170: if (securityDomain.startsWith("java:/jaas/") == false)
171: sec = (SubjectSecurityManager) iniCtx
172: .lookup("java:/jaas/" + securityDomain);
173: else
174: throw e;
175: }
176: interceptor = new ServerSecurityInterceptor(this );
177:
178: idGenerator = new SessionIDGenerator();
179:
180: super .startService();
181: }
182:
183: public void stopService() throws Exception {
184: // Anything to do here?
185: }
186:
187: public String authenticate(String user, String password)
188: throws JMSException {
189: /*
190: try {
191: o = securityCtx.lookup("securityMgr");
192: }catch(NamingException ex) {
193: throw new JMSException("Could not get a security context");
194: }
195: */
196: boolean trace = log.isTraceEnabled();
197: SimplePrincipal principal = new SimplePrincipal(user);
198: char[] passwordChars = null;
199: if (password != null)
200: passwordChars = password.toCharArray();
201: Subject subject = new Subject();
202: if (sec.isValid(principal, passwordChars, subject)) {
203: if (trace)
204: log.trace("Username: " + user + " is authenticated");
205:
206: String sessionId = generateId(subject);
207: addId(sessionId, subject, principal);
208:
209: // Should we log it out since we do not use manager any more?
210: return sessionId;
211: } else {
212: if (trace)
213: log.trace("User: " + user + " is NOT authenticated");
214: throw new JMSSecurityException("User: " + user
215: + " is NOT authenticated");
216: }
217: }
218:
219: public boolean authorize(ConnectionToken token, Set rolePrincipals)
220: throws JMSException {
221: //Unfortunately we can not reliably use the securityManager and its
222: // subject, since can not guarantee that every connection is
223: // connected to a unique thread.
224: // For now we implement the RealmMapping our self
225: boolean trace = log.isTraceEnabled();
226: boolean hasRole = false;
227:
228: SubjectInfo info = (SubjectInfo) authCache.get(token
229: .getSessionId());
230: if (info == null)
231: throw new JMSSecurityException("User session is not valid");
232:
233: if (trace)
234: log.trace("Checking authorize on subjectInfo: "
235: + info.toString() + " for rolePrincipals "
236: + rolePrincipals.toString());
237:
238: Group group = info.roles;
239: if (group != null) {
240: Iterator iter = rolePrincipals.iterator();
241: while (hasRole == false && iter.hasNext()) {
242: Principal role = (Principal) iter.next();
243: hasRole = group.isMember(role);
244: }
245:
246: }
247: return hasRole;
248: }
249:
250: // Is this a security problem? May a bad user set this manually and log out other users?
251: public void logout(ConnectionToken token) {
252: if (token == null)
253: return;
254: // Not much we can do
255: // FIXME - how do we clear the thread local in security manager?
256: removeId(token.getSessionId());
257: }
258:
259: private void addId(String id, Subject subject,
260: Principal callerPrincipal) {
261: boolean trace = log.isTraceEnabled();
262:
263: SubjectInfo info = new SubjectInfo();
264: info.subject = subject;
265: info.principal = callerPrincipal;
266:
267: Set subjectGroups = subject.getPrincipals(Group.class);
268: Iterator iter = subjectGroups.iterator();
269: while (iter.hasNext()) {
270: Group grp = (Group) iter.next();
271: String name = grp.getName();
272: if (name.equals("CallerPrincipal")) {
273: Enumeration members = grp.members();
274: if (members.hasMoreElements())
275: info.principal = (Principal) members.nextElement();
276: } else if (name.equals("Roles")) {
277: if (trace)
278: log.trace("Adding group : " + grp.getClass() + " "
279: + grp.toString());
280: info.roles = grp;
281: }
282: }
283: /* Handle null principals with no callerPrincipal. This is an indication
284: of an user that has not provided any authentication info, but
285: has been authenticated by the domain login module stack. Here we look
286: for the first non-Group Principal and use that.
287: */
288: if (callerPrincipal == null && info.principal == null) {
289: Set subjectPrincipals = subject
290: .getPrincipals(Principal.class);
291: iter = subjectPrincipals.iterator();
292: while (iter.hasNext()) {
293: Principal p = (Principal) iter.next();
294: if ((p instanceof Group) == false)
295: info.principal = p;
296: }
297: }
298:
299: synchronized (authCache) {
300: authCache.put(id, info);
301: }
302: }
303:
304: private void removeId(String id) {
305: synchronized (authCache) {
306: authCache.remove(id);
307: }
308: }
309:
310: private String generateId(Subject subject) throws JMSException {
311: try {
312: return idGenerator.nextSessionId();
313: } catch (Exception ex) {
314: log.error("Could not generate a secure sessionID", ex);
315: //Dont show client the real reason
316: throw new JMSSecurityException(
317: "Could not generate a secure sessionID");
318: }
319: }
320:
321: /**
322: * @see InterceptorMBean#getInterceptor()
323: */
324: public JMSServerInterceptor getInterceptor() {
325: return interceptor;
326: }
327:
328: } // SecurityManager
|