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.test.security.test;
023:
024: import java.security.Principal;
025: import java.security.acl.Group;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Properties;
031: import java.util.Set;
032: import java.util.StringTokenizer;
033:
034: import javax.management.JMException;
035: import javax.management.MBeanServer;
036: import javax.management.ObjectName;
037: import javax.resource.spi.ManagedConnectionFactory;
038: import javax.resource.spi.security.PasswordCredential;
039: import javax.security.auth.Subject;
040: import javax.security.auth.callback.CallbackHandler;
041:
042: import org.jboss.logging.Logger;
043: import org.jboss.mx.util.MBeanServerLocator;
044: import org.jboss.security.RealmMapping;
045: import org.jboss.security.SecurityAssociation;
046: import org.jboss.security.SimpleGroup;
047: import org.jboss.security.SimplePrincipal;
048: import org.jboss.security.SubjectSecurityManager;
049: import org.jboss.util.CachePolicy;
050:
051: //$Id: CustomSecurityManager.java 57211 2006-09-26 12:39:46Z dimitris@jboss.org $
052:
053: /**
054: * JBAS-2703 : Create a AuthenticationManager/AuthorizationManager
055: * plugin testcase
056: *
057: * Security Manager Configuration:
058: * This custom security manager depends on two properties files,
059: * 1. one for the principal to credential mapping and
060: * 2. the other for principal to roles mapping. ( A format of
061: * principal.CallerPrincipal=newPrincipal, provides the mechanism
062: * to specify the caller principal, for a particular principal)
063: *
064: * The property files are custom-users.properties and custom-roles.properties
065: *
066: * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
067: * @since Mar 31, 2006
068: * @version $Revision: 57211 $
069: */
070: public class CustomSecurityManager implements SubjectSecurityManager,
071: RealmMapping {
072: private static Logger log = Logger
073: .getLogger(CustomSecurityManager.class);
074: private String securityDomain;
075: private CallbackHandler callbackHandler;
076: private CachePolicy cachePolicy;
077: private final Principal UNAUTHENTICATED_IDENTITY = new SimplePrincipal(
078: "guest");
079:
080: //Local Configuration
081: private static Map principalToCredMap = new HashMap();
082: private static Map principalToRoleMap = new HashMap();
083: private static Map principalToCallerPrincipalMap = new HashMap();
084:
085: private static final String rolePropertiesFile = "custom-roles.properties";
086: private static final String credPropertiesFile = "custom-users.properties";
087:
088: protected Subject activeSubject;
089:
090: /**
091: *
092: * Create a new CustomSecurityManager.
093: *
094: */
095: public CustomSecurityManager() {
096: super ();
097: }
098:
099: /**
100: *
101: * Create a new CustomSecurityManager.
102: * (Ctr used by the JaasSecurityManagerService to instantiate
103: * the security manager plugin)
104: * @param domain Security Domain
105: * @param handler Callback Handler
106: */
107: public CustomSecurityManager(String domain, CallbackHandler handler) {
108: this ();
109: callbackHandler = handler;
110: securityDomain = domain;
111:
112: //For the JCA cases, there is no need for security checks as they are handled
113: //in the isValid method below.
114: if ("HsqlDbRealm".equals(securityDomain) == false
115: && "jbossmq".equals(securityDomain) == false
116: && "JmsXARealm".equals(securityDomain) == false) {
117: this .fillRoles(securityDomain);
118: this .fillCredsCache(securityDomain);
119: }
120: log.debug("[CallBackHandler=" + handler + ":domain=" + domain
121: + "]");
122: }
123:
124: /**
125: * Sets the Cache Policy
126: * (JaasSecurityManagerService calls this method on the security manager
127: * to plug in a cache policy)
128: *
129: * @param cachePolicy
130: */
131: public void setCachePolicy(CachePolicy cachePolicy) {
132: this .cachePolicy = cachePolicy;
133: }
134:
135: //*******************************************************************
136: // SubjectSecurityManager (AuthenticationManager)
137: // Interface Methods
138: //*******************************************************************
139:
140: /**
141: * @see AuthenticationManager#isValid(Principal , Object )
142: */
143: public boolean isValid(Principal principal, Object credential) {
144: return isValid(principal, credential, null);
145: }
146:
147: /**
148: * @see AuthenticationManager#isValid(Principal, Object, Subject)
149: */
150: public boolean isValid(Principal principal, Object credential,
151: Subject activeSubject) {
152: log.debug("[isValid:principal=" + principal + ":credential="
153: + credential + ":activeSubject=" + activeSubject + "]");
154: boolean result = false;
155:
156: if (principal == null || checkNullSimplePrincipal(principal))
157: principal = UNAUTHENTICATED_IDENTITY;
158:
159: if (activeSubject == null)
160: activeSubject = new Subject();
161:
162: if ("HsqlDbRealm".equals(securityDomain)) {
163: try {
164: handleJCA(activeSubject);
165: result = true;
166: } catch (JMException e) {
167: log.error("Exception in isValid:", e);
168: }
169: } else
170: result = authenticate(principal, credential);
171: if (result) {
172: addRolesInSubject(principal, credential, activeSubject);
173: }
174: return result;
175: }
176:
177: /**
178: * @see AuthenticationManager#getActiveSubject()
179: */
180: public Subject getActiveSubject() {
181: return activeSubject;
182: }
183:
184: /**
185: * @see AuthenticationManager#getSecurityDomain()
186: */
187: public String getSecurityDomain() {
188: return securityDomain;
189: }
190:
191: //*******************************************************************
192: // RealmMapping Interface Methods
193: //*******************************************************************
194:
195: /**
196: * @see RealmMapping#doesUserHaveRole(Principal, Set)
197: */
198: public boolean doesUserHaveRole(Principal principal, Set roles) {
199: log.debug("[doesUserHaveRole:Principal=" + principal
200: + ":rolesCheck=" + roles + "]");
201: Set containedSet = (Set) principalToRoleMap.get(principal);
202: /**
203: * Check for roles only when the principal has roles and also
204: * when the roleset passed is not empty
205: */
206: boolean shouldCheck = containedSet != null && roles != null
207: && !roles.isEmpty();
208: return shouldCheck ? containedSet.containsAll(roles) : false;
209: }
210:
211: /**
212: * @see RealmMapping#getPrincipal(Principal)
213: */
214: public Principal getPrincipal(Principal principal) {
215: Principal callerP = (Principal) principalToCallerPrincipalMap
216: .get(principal);
217: log.debug("[getPrincipal:principal=" + principal + ":callerP="
218: + callerP + "]");
219: return callerP;
220: }
221:
222: /**
223: * @see RealmMapping#getUserRoles(Principal)
224: */
225: public Set getUserRoles(Principal principal) {
226: log.debug("[getUserRoles:Principal=" + principal + "]");
227: return (Set) principalToRoleMap.get(principal);
228: }
229:
230: //*******************************************************************
231: // Private Methods
232: //*******************************************************************
233:
234: /**
235: * Add the roles in the active Subject
236: *
237: * @param principal
238: * @param cred
239: * @param activeSubject
240: */
241: private void addRolesInSubject(Principal principal, Object cred,
242: Subject activeSubject) {
243: //Update the subject with principal and roles
244: Group group = new SimpleGroup("Roles");
245: Set set = (Set) principalToRoleMap.get(principal);
246: if (set != null && !set.isEmpty()) {
247: Iterator iter = set.iterator();
248: while (iter.hasNext()) {
249: group.addMember((Principal) iter.next());
250: }
251: }
252:
253: activeSubject.getPrincipals().add(principal);
254: activeSubject.getPrincipals().add(group);
255: SecurityAssociation.setSubject(activeSubject);
256: SecurityAssociation.setPrincipal(principal);
257: SecurityAssociation.setCredential(cred);
258: }
259:
260: /**
261: * Authenticate the principal/credential against the internal
262: * hashmap (loaded from the users property file)
263: *
264: * @param principal
265: * @param credential
266: * @return
267: */
268: private boolean authenticate(Principal principal, Object credential) {
269: if (principal == null)
270: throw new IllegalArgumentException("Illegal Null Argument:"
271: + principal);
272: boolean result = false;
273: String username = "";
274: String pwd = "";
275: if (principal instanceof SimplePrincipal) {
276: username = ((SimplePrincipal) principal).getName();
277: }
278: if (credential instanceof char[]) {
279: pwd = new String((char[]) credential);
280: } else if (credential instanceof String) {
281: pwd = (String) credential;
282: }
283: String storedCred = (String) this .principalToCredMap
284: .get(principal);
285:
286: if (this .UNAUTHENTICATED_IDENTITY.equals(principal))
287: result = true;
288: else {
289: result = pwd.equals(storedCred);
290: }
291: log.debug("[authenticate:username=" + username + ":pwd=" + pwd
292: + "::storedCred=" + storedCred + ":result=" + result
293: + "]");
294: return result;
295: }
296:
297: /**
298: * Special case with JMS layer that sends a
299: * SimplePrincipal with null username.
300: *
301: * @param p
302: * @return
303: */
304: private boolean checkNullSimplePrincipal(Principal p) {
305: boolean result = false;
306: if (p instanceof SimplePrincipal) {
307: SimplePrincipal sp = (SimplePrincipal) p;
308: if (sp.getName() == null)
309: result = true;
310: }
311: return result;
312: }
313:
314: /**
315: * Read the credentials defined in the users property file
316: * into the internal hashmap
317: *
318: * @param domain
319: */
320: private static void fillCredsCache(String domain) {
321: Properties prop = new Properties();
322: try {
323: prop.load(getThreadContextClassLoader()
324: .getResourceAsStream(credPropertiesFile));
325: Iterator iter = prop.keySet().iterator();
326: while (iter.hasNext()) {
327: String str = (String) iter.next();
328: String pwd = prop.getProperty(str);
329: principalToCredMap.put(new SimplePrincipal(str), pwd);
330: }
331: log.debug("principalToCredSet:" + principalToCredMap);
332: } catch (Exception e) {
333: log.error("Initialization exception:domain=" + domain, e);
334: }
335: }
336:
337: /**
338: * Read the roles defined in the roles property file
339: * into the internal hashmap
340: *
341: * @param domain
342: */
343: private static void fillRoles(String domain) {
344: Properties prop = new Properties();
345: try {
346: prop.load(getThreadContextClassLoader()
347: .getResourceAsStream(rolePropertiesFile));
348: Iterator iter = prop.keySet().iterator();
349: while (iter.hasNext()) {
350: String str = (String) iter.next();
351: String roles = prop.getProperty(str);
352: log.debug("str:before=" + str);
353: Set roleSet = getSet(roles);
354:
355: if (str.indexOf(".Roles") > -1)
356: str = str.substring(0, str.indexOf(".Roles"));
357: if (str.indexOf(".CallerPrincipal") > -1) {
358: str = str.substring(0, str
359: .indexOf(".CallerPrincipal"));
360: principalToCallerPrincipalMap.put(
361: new SimplePrincipal(str),
362: new SimplePrincipal(roles));
363: continue;
364: }
365: Set set = (Set) principalToRoleMap
366: .get(new SimplePrincipal(str));
367: if (set != null)
368: set.addAll(roleSet);
369: else
370: principalToRoleMap.put(new SimplePrincipal(str),
371: roleSet);
372: }
373: log.debug("principalToRoleSet:" + principalToRoleMap);
374: log.debug("principalToCallerPrincipalMap:"
375: + principalToCallerPrincipalMap);
376: } catch (Exception e) {
377: log.error("Initialization exception:domain=" + domain, e);
378: }
379: }
380:
381: /**
382: * Get the TCL
383: *
384: * @return the Thread Context ClassLoader
385: */
386: private static ClassLoader getThreadContextClassLoader() {
387: return Thread.currentThread().getContextClassLoader();
388: }
389:
390: /**
391: * Given a comma-seperated string of roles, returns a set
392: * that contains roles as objects of SimplePrincipal
393: *
394: * @param listOfRoles
395: * @return a java.util.Set of roles as objects of SimplePrincipal
396: */
397: private static Set getSet(String listOfRoles) {
398: Set set = new HashSet();
399: StringTokenizer tokenizer = new StringTokenizer(listOfRoles,
400: ",");
401: while (tokenizer.hasMoreTokens()) {
402: set.add(new SimplePrincipal(tokenizer.nextToken().trim()));
403: }
404: return set;
405: }
406:
407: /**
408: * For the JCA case (HsqdbRealm), we will hardcode stuff
409: */
410: private void handleJCA(Subject activeSubject) throws JMException {
411: MBeanServer server = MBeanServerLocator.locateJBoss();
412: PasswordCredential pc = new PasswordCredential("sa",
413: new char[0]);
414: ManagedConnectionFactory mcf = (ManagedConnectionFactory) server
415: .getAttribute(new ObjectName(
416: "jboss.jca:service=LocalTxCM,name=DefaultDS"),
417: "ManagedConnectionFactory");
418: pc.setManagedConnectionFactory(mcf);
419: String principalStr = "sa";
420: Principal principal = new SimplePrincipal(principalStr);
421: activeSubject.getPrincipals().add(principal);
422: activeSubject.getPrivateCredentials().add(pc);
423: }
424: }
|