Re: cvs commit: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm RealmBase.java
Hi, I've been refactoring the piece of code I sent last time, and I've got that now : - the following method just replaces the findSecurityConstraints(HttpRequest request, Context context) method in RealmBase - there is this static Map cache which caches the fake Constraints mapped to (URI, http-method) couples to add - lastly, the matchPattern(String path, String pattern) must be made public. It works OK for me, hope it helps Philippe Leothaud private static Map cache = new HashMap(); private final static String NULL_CONSTRAINT = ""; /** * Retrieve from cache or build and return a custom SecurityConstraint merging all valid * SecurityConstraints for the given method and URI, or null * if there is no such SecurityConstraint * * @param allConstraints : all the SecurityConstraints defined in web.xml * @param req: the request of the User * * @return : the custom SecurityConstraint, wrapped in an array of * SecurityConstraints (to not break AuthenticatorBase and RealmBase API) */ public SecurityConstraint[] findSecurityConstraints(HttpRequest request, Context context) { // Get allConstraints Context SecurityConstraint allConstraints[] = context.findConstraints(); if ((allConstraints == null) || (allConstraints.length == 0)) { if (log.isDebugEnabled()) log.debug(" No applicable constraints defined"); return (null); } // Get URI and method from request HttpServletRequest hreq = (HttpServletRequest) request.getRequest(); String requestURI = request.getDecodedRequestURI(); String contextPath = hreq.getContextPath(); if (contextPath.length() > 0) requestURI = requestURI.substring(contextPath.length()); String method = hreq.getMethod(); if ((allConstraints == null) || (allConstraints.length == 0)) { if (log.isDebugEnabled()) log.debug(" No applicable constraints defined"); return (null); } // Did we already do the job ? Object cached = cache.get(requestURI + "::" + method); // No : let's work a bit if(cached == null) { // Determining valid constraints, checking the constraints' url-patterns against the given requestURI Map constraintsAndCollections = null; String bestMatch = ""; for (int i = 0; i < allConstraints.length; i++) { String constraintBestMatch = ""; SecurityCollection[] collections = allConstraints[i].findCollections(); for (int j = 0; j < collections.length; j++) { String patterns[] = collections[j].findPatterns(); for (int k = 0; k < patterns.length; k++) if (allConstraints[i].matchPattern(requestURI, patterns[k]) && patterns[k].length() > bestMatch.length()) bestMatch = patterns[k]; } if (constraintBestMatch.length() > bestMatch.length()) bestMatch = constraintBestMatch; } for (int i = 0; i < allConstraints.length; i++) { SecurityCollection[] collections = allConstraints[i].findCollections(); List matchingWebCollections = null; for (int j = 0; j < collections.length; j++) { String patterns[] = collections[j].findPatterns(); for (int k = 0; k < patterns.length; k++) { if (bestMatch.equals(patterns[k])) { if(matchingWebCollections == null) matchingWebCollections = new ArrayList(); matchingWebCollections.add(collections[j]); break; } } } if (matchingWebCollections != null) { if (constraintsAndCollections == null) constraintsAndCollections = new HashMap(); constraintsAndCollections.put(allConstraints[i], matchingWebCollections); } } if (constraintsAndCollections == null) { cache.put(requestURI + "::" + method, NULL_CONSTRAINT); return null; } // Determining valid constraints, checking the constraints' constrained methods against the given method Set matchingConstraints = constraintsAndCollections.keySet(); Iterator matchingConstraintsIterator = matchingConstraints.iterator(); while (matchingConstraintsIterator.hasNext()) { SecurityConstraint constraint = (SecurityConstraint) matchingConstraintsIterator.next(); List matchingWebCollections = (List) constraintsAndCollections.get(constraint); Iterator matchingWebCollectionsIterator = matchingWebCollections.iterator(); boolean methodIsProtected = false; while (matchingWebCollectionsIterator.hasNext()) { SecurityCollection collection = (SecurityCollection) matchingWebCollectionsIterator.next(); String[] constrainedMethods = collection.findMethods(); if (constrainedMethods == null || constrainedMethods.length == 0) { methodIsProtected = true; break; } for (int i = 0; i < constrainedMethods.length; i++) { if (method.equals(constrainedMethods[i])) { methodIsProtected = true; break; } } if (methodIsProtected) break; } if (!methodIsProtected) matchingConstraintsIterator.
Tomcat authorization handling seems not to function according to Servlet 2.4 Spec
Hi all, I am new to Tomcat's mailing lists, and I don't really know if this list is the right place for such a post : excuse me if it is not the case. I wonder if I didn't notice something which is not a real bug in Tomcat, as it seems to do exactly what developers want it to do, but more a difference between the implementation of authorization policy (the handling of a Web Application web.xml security-constraint elements) in Tomcat5 and what the Servlet 2.4 Spec says. Example of the problem (from the Tomcat Jsp-examples WebApp) : tomcat-users.xml Example Security Constraint Protected Area /security/protected/* DELETE GET POST PUT tomcat Example Security Constraint Protected Area /security/protected/* DELETE GET POST PUT role1 FORM Example Form-Based Authentication Area /security/protected/login.jsp /security/protected/error.jsp role1 tomcat webapps/jsp-examples/WEB-INF/web.xml (excerpt) I've been adding a new security-constraint element, separing the authorized roles each in its security-constraint According to what the Servlet 2.4 says (see below for exact reference), two security constraints on the same (url-pattern, http-method) should result in the addition of the given authorizations and so in this case, users "tomcat", "role1" and "both" should be authorized to access the protected resource. But here, it is the contrary : you can't access http://10.160.4.205:8080/jsp-examples/security/protected/ under "tomcat" or "role1" identity any more, but you can still using the "both" identity : Tomcat has realized the intersection of the authorizations instead of doing the union. Analyze of the problem After inverstigating a while in the code, here is what I noticed : First, In SecurityConstraint[] RealmBase.findSecurityConstraints(HttpRequest request, Context context) (the method begins at l. 445 of the org.apache.catalina.realm.RealmBase file), each and every SecurityConstraint (<=> security-constraint in web.xml) containing a SecurityCollection (<=> web-ressource-collection in web.xml) containing a url-pattern matching the User's request URI and defining a restriction on the http-method used by the user for his request is retrieved, using boolean SecurityConstraint.included(String uri, String method) (method starts at line 343 of org.apache.catalina.deploy.SecurityConstraint) While only SecurityConstraints containing SecurityCollections containing the url-pattern which is the best-match to the User's request URI amongst all the url-patterns defined in web.xml should be retained first, and then amongst these remaining constraints we shall keep only the ones defining a restriction on the same method (or no restriction on the method, as stated in servlet-2_4-fr-spec.pdf, ch SRV 12-8-3, pp 100-101) Second in public boolean hasResourcePermission(HttpRequest request, HttpResponse response, SecurityConstraint constraint, Context context) (the method begins at line 501 of the org.apache.catalina.realm.RealmBase file) the restrictions on the authorized groups are analyzed, constraint after constraint, and as soon as one constraint is not verified, response.getResponse()).sendError( HttpServletResponse.SC_FORBIDDEN, sm.getString("realmBase.forbidden")); is sent to the User : this means that at the contrary of what the spec says, for a same (http-method, url-pattern) couple, it's not the union of the authorizations but the intersection that is realized. Spec : The rules to combine roles are given in servlet-2_4-fr-spec.pdf, ch SRV 12.8.1, pp97-98 : "The combination of authorization constraints that name roles or that imply roles via the name shall yield the union of the role names in the individual constraints as permitted roles. A security constraint that does not contain an authorization constraint shall combine with authorization constraints that name or imply roles to allow unauthenticated access. The special case of an authorization constraint that names no roles shall combine with any other constraints to override their affects and cause access to be precluded." Third A similar problem as the second one accurs in the call to public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint constraint) (the method begins at line 627 of the org.apache.catalina.realm.RealmBase file) As in the second point, constraints are examined one by one, instead of determining globally the policy for all the constraints applying for the same (http-method, url-patern) Spec :The rules to combine user-data-constraints are given in servlet-2_4-fr-spec.pdf, ch SRV 12.8.1, p98 : "The combination of u