Re: cvs commit: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/realm RealmBase.java

2003-12-11 Thread philippe.leothaud
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

2003-12-08 Thread philippe.leothaud
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