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.web;
023:
024: import java.util.Iterator;
025: import java.util.HashMap;
026: import java.util.HashSet;
027: import java.util.Arrays;
028: import java.util.Map;
029: import java.util.Set;
030: import java.util.ArrayList;
031: import java.util.Collection;
032: import javax.security.jacc.PolicyConfiguration;
033: import javax.security.jacc.PolicyContextException;
034: import javax.security.jacc.WebResourcePermission;
035: import javax.security.jacc.WebUserDataPermission;
036: import javax.security.jacc.WebRoleRefPermission;
037:
038: import org.jboss.metadata.WebMetaData;
039: import org.jboss.metadata.WebSecurityMetaData;
040: import org.jboss.metadata.SecurityRoleRefMetaData;
041: import org.jboss.logging.Logger;
042:
043: /**
044: * A utility class encapsulating the logic for building the web container JACC
045: * permission from a deployment's metadata.
046: *
047: * @author Scott.Stark@jboss.org
048: * @version $Revision: 60958 $
049: */
050: public class WebPermissionMapping {
051: static Logger log = Logger.getLogger(WebPermissionMapping.class);
052:
053: /** An prefix pattern "/prefix/*" */
054: private static final int PREFIX = 1;
055: /** An extension pattern "*.ext" */
056: private static final int EXTENSION = 2;
057: /** The "/" default pattern */
058: private static final int DEFAULT = 3;
059: /** An prefix pattern "/prefix/*" */
060: private static final int EXACT = 4;
061:
062: /**
063: * Apply the JACC rules for creating permissions from the web.xml
064: * security-constraints.
065: *
066: * @param metaData - the web deployment web.xml/jboss-web.xml metadata
067: * @param pc - the active JACC policy configuration
068: * @throws PolicyContextException
069: */
070: public static void createPermissions(WebMetaData metaData,
071: PolicyConfiguration pc) throws PolicyContextException {
072: HashMap patternMap = qualifyURLPatterns(metaData);
073: log.debug("Qualified url patterns: " + patternMap);
074:
075: Iterator constraints = metaData.getSecurityContraints();
076: while (constraints.hasNext()) {
077: WebSecurityMetaData wsmd = (WebSecurityMetaData) constraints
078: .next();
079: String transport = wsmd.getTransportGuarantee();
080: if (wsmd.isExcluded() || wsmd.isUnchecked()) {
081: // Process the permissions for the excluded/unchecked resources
082: Iterator resources = wsmd.getWebResources().values()
083: .iterator();
084: while (resources.hasNext()) {
085: WebSecurityMetaData.WebResourceCollection wrc = (WebSecurityMetaData.WebResourceCollection) resources
086: .next();
087: String[] httpMethods = wrc.getHttpMethods();
088: String[] urlPatterns = wrc.getUrlPatterns();
089: for (int n = 0; n < urlPatterns.length; n++) {
090: String url = urlPatterns[n];
091: PatternInfo info = (PatternInfo) patternMap
092: .get(url);
093: // Add the excluded methods
094: if (wsmd.isExcluded()) {
095: info.addExcludedMethods(httpMethods);
096: }
097: }
098: }
099: } else {
100: // Process the permission for the resources x roles
101: Iterator resources = wsmd.getWebResources().values()
102: .iterator();
103: while (resources.hasNext()) {
104: WebSecurityMetaData.WebResourceCollection wrc = (WebSecurityMetaData.WebResourceCollection) resources
105: .next();
106: String[] httpMethods = wrc.getHttpMethods();
107: String[] urlPatterns = wrc.getUrlPatterns();
108: for (int n = 0; n < urlPatterns.length; n++) {
109: String url = urlPatterns[n];
110: // Get the qualified url pattern
111: PatternInfo info = (PatternInfo) patternMap
112: .get(url);
113: Iterator roles = wsmd.getRoles().iterator();
114: HashSet mappedRoles = new HashSet();
115: while (roles.hasNext()) {
116: String role = (String) roles.next();
117: if (role.equals("*")) {
118: //JBAS-1824: Allow "*" to provide configurable authorization bypass
119: if (metaData.isJaccRoleNameStar())
120: mappedRoles.add("*");
121: else {
122: //The wildcard ref maps to all declared security-role names
123: Iterator allRoles = metaData
124: .getSecurityRoleNames()
125: .iterator();
126: while (allRoles.hasNext()) {
127: role = (String) allRoles.next();
128: mappedRoles.add(role);
129: }
130: }
131: } else {
132: mappedRoles.add(role);
133: }
134: }
135: info.addRoles(mappedRoles, httpMethods);
136: // Add the transport to methods
137: info.addTransport(transport, httpMethods);
138: }
139: }
140: }
141: }
142:
143: // Create the permissions
144: Iterator iter = patternMap.values().iterator();
145: while (iter.hasNext()) {
146: PatternInfo info = (PatternInfo) iter.next();
147: String qurl = info.getQualifiedPattern();
148: if (info.isOverriden == true) {
149: log.debug("Dropping overriden pattern: " + info);
150: continue;
151: }
152:
153: // Create the excluded permissions
154: String[] httpMethods = info.getExcludedMethods();
155: if (httpMethods != null) {
156: // There were excluded security-constraints
157: WebResourcePermission wrp = new WebResourcePermission(
158: qurl, httpMethods);
159: WebUserDataPermission wudp = new WebUserDataPermission(
160: qurl, httpMethods, null);
161: pc.addToExcludedPolicy(wrp);
162: pc.addToExcludedPolicy(wudp);
163: }
164:
165: // Create the role permissions
166: Iterator roles = info.getRoleMethods();
167: while (roles.hasNext()) {
168: Map.Entry roleMethods = (Map.Entry) roles.next();
169: String role = (String) roleMethods.getKey();
170: WebResourcePermission wrp = null;
171: if ("*".equals(role)) {
172: //JBAS-1824: <role-name>*</role-name>
173: wrp = new WebResourcePermission(qurl, (String) null);
174: } else {
175: HashSet methods = (HashSet) roleMethods.getValue();
176: httpMethods = new String[methods.size()];
177: methods.toArray(httpMethods);
178: wrp = new WebResourcePermission(qurl, httpMethods);
179: }
180: pc.addToRole(role, wrp);
181: }
182:
183: // Create the unchecked permissions
184: String[] missingHttpMethods = info.getMissingMethods();
185: if (missingHttpMethods.length > 0) {
186: // Create the unchecked permissions WebResourcePermissions
187: WebResourcePermission wrp = new WebResourcePermission(
188: qurl, missingHttpMethods);
189: pc.addToUncheckedPolicy(wrp);
190:
191: }
192:
193: // Create the unchecked permissions WebUserDataPermissions
194: Iterator transportContraints = info.getTransportMethods();
195: while (transportContraints.hasNext()) {
196: Map.Entry transportMethods = (Map.Entry) transportContraints
197: .next();
198: String transport = (String) transportMethods.getKey();
199: Set methods = (Set) transportMethods.getValue();
200: httpMethods = new String[methods.size()];
201: methods.toArray(httpMethods);
202: if (info.getExcludedMethods() == null) {
203: WebUserDataPermission wudp = new WebUserDataPermission(
204: qurl, httpMethods, transport);
205: pc.addToUncheckedPolicy(wudp);
206:
207: if ("NONE".equals(transport)) {
208: WebUserDataPermission wudp1 = new WebUserDataPermission(
209: info.pattern, null);
210: pc.addToUncheckedPolicy(wudp1);
211: }
212: }
213: }
214: }
215:
216: /* Create WebRoleRefPermissions for all servlet/security-role-refs along
217: with all the cross product of servlets and security-role elements that
218: are not referenced via a security-role-ref as described in JACC section
219: 3.1.3.2
220: */
221: Set unreferencedRoles = metaData.getSecurityRoleNames();
222: Map servletRoleRefs = metaData.getSecurityRoleRefs();
223: Iterator roleRefsIter = servletRoleRefs.keySet().iterator();
224: while (roleRefsIter.hasNext()) {
225: String servletName = (String) roleRefsIter.next();
226: ArrayList roleRefs = (ArrayList) servletRoleRefs
227: .get(servletName);
228: for (int n = 0; n < roleRefs.size(); n++) {
229: SecurityRoleRefMetaData roleRef = (SecurityRoleRefMetaData) roleRefs
230: .get(n);
231: String roleName = roleRef.getLink();
232: WebRoleRefPermission wrrp = new WebRoleRefPermission(
233: servletName, roleRef.getName());
234: pc.addToRole(roleName, wrrp);
235: /* A bit of a hack due to how tomcat calls out to its Realm.hasRole()
236: with a role name that has been mapped to the role-link value. We
237: may need to handle this with a custom request wrapper.
238: */
239: wrrp = new WebRoleRefPermission(servletName, roleName);
240: pc.addToRole(roleRef.getName(), wrrp);
241: // Remove the role from the unreferencedRoles
242: unreferencedRoles.remove(roleName);
243: }
244: }
245:
246: // Now build the cross product of the unreferencedRoles and servlets
247: Set servletNames = metaData.getServletNames();
248: Iterator names = servletNames.iterator();
249: while (names.hasNext()) {
250: String servletName = (String) names.next();
251: Iterator roles = unreferencedRoles.iterator();
252: while (roles.hasNext()) {
253: String role = (String) roles.next();
254: WebRoleRefPermission wrrp = new WebRoleRefPermission(
255: servletName, role);
256: pc.addToRole(role, wrrp);
257: }
258: }
259: /**
260: * The programmatic security checks are made from jsps.
261: * JBAS-3054:Use of isCallerInRole from jsp does not work for JACC
262: */
263: Iterator roles = unreferencedRoles.iterator();
264: while (roles.hasNext()) {
265: String role = (String) roles.next();
266: WebRoleRefPermission wrrp = new WebRoleRefPermission("",
267: role);
268: pc.addToRole(role, wrrp);
269: }
270: }
271:
272: /**
273: * Determine the url-pattern type
274: * @param urlPattern - the raw url-pattern value
275: * @return one of EXACT, EXTENSION, PREFIX, DEFAULT
276: */
277: static int getPatternType(String urlPattern) {
278: int type = EXACT;
279: if (urlPattern.startsWith("*."))
280: type = EXTENSION;
281: else if (urlPattern.startsWith("/")
282: && urlPattern.endsWith("/*"))
283: type = PREFIX;
284: else if (urlPattern.equals("/"))
285: type = DEFAULT;
286: return type;
287: }
288:
289: /**
290: JACC url pattern Qualified URL Pattern Names.
291:
292: The rules for qualifying a URL pattern are dependent on the rules for
293: determining if one URL pattern matches another as defined in Section 3.1.3.3,
294: Servlet URL-Pattern Matching Rules, and are described as follows:
295: - If the pattern is a path prefix pattern, it must be qualified by every
296: path-prefix pattern in the deployment descriptor matched by and different from
297: the pattern being qualified. The pattern must also be qualified by every exact
298: pattern appearing in the deployment descriptor that is matched by the pattern
299: being qualified.
300: - If the pattern is an extension pattern, it must be qualified by every
301: path-prefix pattern appearing in the deployment descriptor and every exact
302: pattern in the deployment descriptor that is matched by the pattern being
303: qualified.
304: - If the pattern is the default pattern, "/", it must be qualified by every
305: other pattern except the default pattern appearing in the deployment descriptor.
306: - If the pattern is an exact pattern, its qualified form must not contain any
307: qualifying patterns.
308:
309: URL patterns are qualified by appending to their String representation, a
310: colon separated representation of the list of patterns that qualify the pattern.
311: Duplicates must not be included in the list of qualifying patterns, and any
312: qualifying pattern matched by another qualifying pattern may5 be dropped from
313: the list.
314:
315: Any pattern, qualified by a pattern that matches it, is overridden and made
316: irrelevant (in the translation) by the qualifying pattern. Specifically, all
317: extension patterns and the default pattern are made irrelevant by the presence
318: of the path prefix pattern "/*" in a deployment descriptor. Patterns qualified
319: by the "/*" pattern violate the URLPatternSpec constraints of
320: WebResourcePermission and WebUserDataPermission names and must be rejected by
321: the corresponding permission constructors.
322:
323: @param metaData - the web deployment metadata
324: @return HashMap<String, PatternInfo>
325: */
326: static HashMap qualifyURLPatterns(WebMetaData metaData) {
327: ArrayList prefixList = new ArrayList();
328: ArrayList extensionList = new ArrayList();
329: ArrayList exactList = new ArrayList();
330: HashMap patternMap = new HashMap();
331: PatternInfo defaultInfo = null;
332:
333: Iterator constraints = metaData.getSecurityContraints();
334: while (constraints.hasNext()) {
335: WebSecurityMetaData wsmd = (WebSecurityMetaData) constraints
336: .next();
337: Iterator resources = wsmd.getWebResources().values()
338: .iterator();
339: while (resources.hasNext()) {
340: WebSecurityMetaData.WebResourceCollection wrc = (WebSecurityMetaData.WebResourceCollection) resources
341: .next();
342: String[] urlPatterns = wrc.getUrlPatterns();
343: for (int n = 0; n < urlPatterns.length; n++) {
344: String url = urlPatterns[n];
345: int type = getPatternType(url);
346: PatternInfo info = (PatternInfo) patternMap
347: .get(url);
348: if (info == null) {
349: info = new PatternInfo(url, type);
350: patternMap.put(url, info);
351: switch (type) {
352: case PREFIX:
353: prefixList.add(info);
354: break;
355: case EXTENSION:
356: extensionList.add(info);
357: break;
358: case EXACT:
359: exactList.add(info);
360: break;
361: case DEFAULT:
362: defaultInfo = info;
363: break;
364: }
365: }
366: }
367: }
368: }
369:
370: // Qualify all prefix patterns
371: for (int i = 0; i < prefixList.size(); i++) {
372: PatternInfo info = (PatternInfo) prefixList.get(i);
373: // Qualify by every other prefix pattern matching this pattern
374: for (int j = 0; j < prefixList.size(); j++) {
375: if (i == j)
376: continue;
377: PatternInfo other = (PatternInfo) prefixList.get(j);
378: if (info.matches(other))
379: info.addQualifier(other);
380: }
381: // Qualify by every exact pattern that is matched by this pattern
382: for (int j = 0; j < exactList.size(); j++) {
383: PatternInfo other = (PatternInfo) exactList.get(j);
384: if (info.matches(other))
385: info.addQualifier(other);
386: }
387: }
388:
389: // Qualify all extension patterns
390: for (int i = 0; i < extensionList.size(); i++) {
391: PatternInfo info = (PatternInfo) extensionList.get(i);
392: // Qualify by every path prefix pattern
393: for (int j = 0; j < prefixList.size(); j++) {
394: PatternInfo other = (PatternInfo) prefixList.get(j);
395: {
396: // Any extension
397: info.addQualifier(other);
398: }
399: }
400: // Qualify by every matching exact pattern
401: for (int j = 0; j < exactList.size(); j++) {
402: PatternInfo other = (PatternInfo) exactList.get(j);
403: if (info.isExtensionFor(other))
404: info.addQualifier(other);
405: }
406: }
407:
408: // Qualify the default pattern
409: if (defaultInfo == null) {
410: defaultInfo = new PatternInfo("/", DEFAULT);
411: patternMap.put("/", defaultInfo);
412: }
413: Iterator iter = patternMap.values().iterator();
414: while (iter.hasNext()) {
415: PatternInfo info = (PatternInfo) iter.next();
416: if (info == defaultInfo)
417: continue;
418: defaultInfo.addQualifier(info);
419: }
420:
421: return patternMap;
422: }
423:
424: /**
425: * A representation of all security-constraint mappings for a unique
426: * url-pattern
427: */
428: static class PatternInfo {
429: static final HashMap ALL_TRANSPORTS = new HashMap();
430: static {
431: ALL_TRANSPORTS.put("NONE",
432: WebSecurityMetaData.ALL_HTTP_METHODS);
433: }
434:
435: /** The raw url-pattern string from the web.xml */
436: String pattern;
437: /** The qualified url pattern as determined by qualifyURLPatterns */
438: String qpattern;
439: /** The list of qualifying patterns as determined by qualifyURLPatterns */
440: ArrayList qualifiers = new ArrayList();
441: /** One of PREFIX, EXTENSION, DEFAULT, EXACT */
442: int type;
443: /** HashSet<String> Union of all http methods seen in excluded statements */
444: HashSet excludedMethods;
445: /** HashMap<String, HashSet<String>> role to http methods */
446: HashMap roles;
447: /** HashMap<String, HashSet<String>> transport to http methods */
448: HashMap transports;
449: // The url pattern to http methods for patterns for
450: HashSet allMethods = new HashSet();
451: /** Does a qualifying pattern match this pattern and make this pattern
452: * obsolete?
453: */
454: boolean isOverriden;
455:
456: /**
457: * @param pattern - the url-pattern value
458: * @param type - one of EXACT, EXTENSION, PREFIX, DEFAULT
459: */
460: PatternInfo(String pattern, int type) {
461: this .pattern = pattern;
462: this .type = type;
463: }
464:
465: /**
466: * Augment the excluded methods associated with this url
467: * @param httpMethods
468: */
469: void addExcludedMethods(String[] httpMethods) {
470: Collection methods = Arrays.asList(httpMethods);
471: if (methods.size() == 0)
472: methods = WebSecurityMetaData.ALL_HTTP_METHODS;
473: if (excludedMethods == null)
474: excludedMethods = new HashSet();
475: excludedMethods.addAll(methods);
476: allMethods.addAll(methods);
477: }
478:
479: /**
480: * Get the list of excluded http methods
481: * @return excluded http methods if the exist, null if there were no
482: * excluded security constraints
483: */
484: public String[] getExcludedMethods() {
485: String[] httpMethods = null;
486: if (excludedMethods != null) {
487: httpMethods = new String[excludedMethods.size()];
488: excludedMethods.toArray(httpMethods);
489: }
490: return httpMethods;
491: }
492:
493: /**
494: * Update the role to http methods mapping for this url.
495: * @param mappedRoles - the role-name values for the auth-constraint
496: * @param httpMethods - the http-method values for the web-resource-collection
497: */
498: public void addRoles(HashSet mappedRoles, String[] httpMethods) {
499: Collection methods = Arrays.asList(httpMethods);
500: if (methods.size() == 0)
501: methods = WebSecurityMetaData.ALL_HTTP_METHODS;
502: allMethods.addAll(methods);
503: if (roles == null)
504: roles = new HashMap();
505:
506: Iterator iter = mappedRoles.iterator();
507: while (iter.hasNext()) {
508: String role = (String) iter.next();
509: HashSet roleMethods = (HashSet) roles.get(role);
510: if (roleMethods == null) {
511: roleMethods = new HashSet();
512: roles.put(role, roleMethods);
513: }
514: roleMethods.addAll(methods);
515: }
516: }
517:
518: /**
519: * Get the role to http method mappings
520: * @return Iterator<Map.Entry<String, HasSet<String>>> for the role
521: * to http method mappings.
522: */
523: public Iterator getRoleMethods() {
524: HashMap tmp = roles;
525: if (tmp == null)
526: tmp = new HashMap(0);
527: Iterator iter = tmp.entrySet().iterator();
528: return iter;
529: }
530:
531: /**
532: * Update the role to http methods mapping for this url.
533: * @param transport - the transport-guarantee value
534: * @param httpMethods - the http-method values for the web-resource-collection
535: */
536: void addTransport(String transport, String[] httpMethods) {
537: Collection methods = Arrays.asList(httpMethods);
538: if (methods.size() == 0)
539: methods = WebSecurityMetaData.ALL_HTTP_METHODS;
540: if (transports == null)
541: transports = new HashMap();
542:
543: HashSet transportMethods = (HashSet) transports
544: .get(transport);
545: if (transportMethods == null) {
546: transportMethods = new HashSet();
547: transports.put(transport, transportMethods);
548: }
549: transportMethods.addAll(methods);
550: }
551:
552: /**
553: * Get the transport to http method mappings
554: * @return Iterator<Map.Entry<String, HasSet<String>>> for the transport
555: * to http method mappings.
556: */
557: public Iterator getTransportMethods() {
558: HashMap tmp = transports;
559: if (tmp == null)
560: tmp = ALL_TRANSPORTS;
561: Iterator iter = tmp.entrySet().iterator();
562: return iter;
563: }
564:
565: /**
566: * Get the list of http methods that were not associated with an excluded
567: * or role based mapping of this url.
568: *
569: * @return the subset of http methods that should be unchecked
570: */
571: public String[] getMissingMethods() {
572: String[] httpMethods = {};
573: if (allMethods.size() == 0) {
574: // There were no excluded or role based security-constraints
575: httpMethods = WebSecurityMetaData.ALL_HTTP_METHOD_NAMES;
576: } else {
577: httpMethods = WebSecurityMetaData
578: .getMissingHttpMethods(allMethods);
579: }
580: return httpMethods;
581: }
582:
583: /**
584: * Add the qualifying pattern. If info is a prefix pattern that matches
585: * this pattern, it overrides this pattern and will exclude it from
586: * inclusion in the policy.
587: *
588: * @param info - a url pattern that should qualify this pattern
589: */
590: void addQualifier(PatternInfo info) {
591: if (qualifiers.contains(info) == false) {
592: // See if this pattern is matched by the qualifier
593: if (info.type == PREFIX && info.matches(this ))
594: isOverriden = true;
595: qualifiers.add(info);
596: }
597: }
598:
599: /**
600: * Get the url pattern with its qualifications
601: * @see WebPermissionMapping#qualifyURLPatterns(org.jboss.metadata.WebMetaData)
602: * @return the qualified form of the url pattern
603: */
604: public String getQualifiedPattern() {
605: if (qpattern == null) {
606: StringBuffer tmp = new StringBuffer(pattern);
607: for (int n = 0; n < qualifiers.size(); n++) {
608: tmp.append(':');
609: PatternInfo info = (PatternInfo) qualifiers.get(n);
610: tmp.append(info.pattern);
611: }
612: qpattern = tmp.toString();
613: }
614: return qpattern;
615: }
616:
617: public int hashCode() {
618: return pattern.hashCode();
619: }
620:
621: public boolean equals(Object obj) {
622: PatternInfo pi = (PatternInfo) obj;
623: return pattern.equals(pi.pattern);
624: }
625:
626: /**
627: * See if this pattern is matches the other pattern
628: * @param other - another pattern
629: * @return true if the other pattern starts with this
630: * pattern less the "/*", false otherwise
631: */
632: public boolean matches(PatternInfo other) {
633: int matchLength = pattern.length() - 2;
634: boolean matches = pattern.regionMatches(0, other.pattern,
635: 0, matchLength);
636: return matches;
637: }
638:
639: /**
640: * See if this is an extension pattern that matches other
641: * @param other - another pattern
642: * @return true if is an extension pattern and other ends with this
643: * pattern
644: */
645: public boolean isExtensionFor(PatternInfo other) {
646: int offset = other.pattern.lastIndexOf('.');
647: int length = pattern.length() - 1;
648: boolean isExtensionFor = false;
649: if (offset > 0) {
650: isExtensionFor = pattern.regionMatches(1,
651: other.pattern, offset, length);
652: }
653: return isExtensionFor;
654: }
655:
656: public String toString() {
657: StringBuffer tmp = new StringBuffer("PatternInfo[");
658: tmp.append("pattern=");
659: tmp.append(pattern);
660: tmp.append(",type=");
661: tmp.append(type);
662: tmp.append(",isOverriden=");
663: tmp.append(isOverriden);
664: tmp.append(",qualifiers=");
665: tmp.append(qualifiers);
666: tmp.append("]");
667: return tmp.toString();
668: }
669:
670: }
671: }
|