Bug 12428

Summary: request.getUserPrincipal(): Misinterpretation of specification?
Product: Tomcat 7 Reporter: Dylan van Iersel <dylan>
Component: CatalinaAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: enhancement CC: brian.bonner, cho, ephemeris.lappis, ttps, werner.donne
Priority: P3    
Version: trunk   
Target Milestone: ---   
Hardware: PC   
OS: All   
Attachments: Filter attempting to store principal in the session
Make authentication independent of security constraints
org.apache.catalina.authenticator.AuthenticatorBase
org.apache.catalina.startup.ContextConfig

Description Dylan van Iersel 2002-09-09 09:28:17 UTC
When calling request.getUserPrincipal() from an unprotected resource, the method returns 
null even when the user is actually authenticated.

From Servlet 2.3 spec:
public 
java.security.Principal getUserPrincipal()
Returns a java.security.Principal object 
containing the name of the current
authenticated user. If the user has not been authenticated, 
the method returns
null.

My interpretation would be that once a user has been 
authenticated, a call to getUserPrincipal() would always return the associated Principal 
object whether it is from a protected or unprotected resource.
Comment 1 Alex Paransky 2003-09-28 19:11:57 UTC
This is a major problem for us porting our application.  We have a menu system
which stays the same for all the users.  Based on the role and if we have a
principal or not, the menu changes with more or less options.
Comment 2 william.barker 2003-09-29 02:30:24 UTC
I don't see anything in the 2.3 spec that precludes the way that Tomcat handles 
this.  The 2.4 spec is a bit more ambiguous, so I'm going to have to try to get 
a clarification from the expert-group before marking this as INVALID.

As a work-around, try using a simple Filter something like:
   public void doFilter(ServletRequest req, ServletResponse res, FilterChain 
chain)
      throws IOException, ServletException {
      HttpServletRequest request = (HttpServletRequest)req;
      Principal userPrin = request.getUserPrincipal();
      if(userPrin == null) {
          HttpSession session = request.getSession(true);
          Principal myPrin = (Principal)session.getAttribute
("com.myfirm.MyPrincipal");
          if(myPrin != null) {
            req = new MyAuthRequest(myPrin);
          }
      } else {
          HttpSession session = request.getSession(true);
          session.setAttribute("com.myfirm.MyPrincipal", userPrin);
      }
      chain.doFilter(req, res);
   }
   static class MyAuthRequest extends HttpServletRequestWrapper {
      Principal myPrin;
      MyAuthRequest(Principal prin) {
      myPrin = prin;
      }
      public Principal getUserPrincipal() {
         return myPrin;
      }
   }

Comment 3 Alex Paransky 2003-09-29 03:16:16 UTC
This works, but only in the context of the WEB container.  If my WEB application
calls down to my entity beans, I am out of luck.  I don't know of a way to pass
this "context" down to the entity beans without having to implicitly pass a user
parameter to ALL the functions which would normally get a principal from the
context.

By the way, this is happening with JBOSS 3.2.2RC3 which has TomCat version 4.1
bundled with it (I am not sure which one).

In my application, additional features become available when user becomes a
member.  Thus all of my code is based on:

a) Do I have a principal, no? then GUEST access otherwise b)
b) If role X is enabled, allow functions X1...Xn

Not having principal in the public pages, does not allow me to check the role.  

I noticed that I am not the only one who is having an issue with the way this
works.  Is there a way to make a parameter, that when set, would pass the
principal to the public pages.  If the parameter is not set, then don't pass the
principal as it's now?  Since the spec is so ambiguous, it makes migration to
TomCat/JBOSS combo from Weblogic, Orion or other app servers difficult.

Thanks.
Comment 4 Alex Paransky 2003-09-29 08:20:06 UTC
Created attachment 8384 [details]
Filter attempting to store principal in the session
Comment 5 Alex Paransky 2003-09-29 13:17:54 UTC
I tried your suggestion, code attached, but it did not work.  Turns out that the
filter is getting called pretty late in the game.  By the time the filter is
called, a null Principal was already seen and configured in to the "context".  I
need to work at the Pipeline/Valve level to get this to work.  
Comment 6 Tim Funk 2004-02-07 15:14:47 UTC
Ya  - this is a problem but tomcat is compatible with the spec. (So its a spec
problem) The pricipal only needs to be set on protected URLs.
Comment 7 Tim Funk 2004-02-07 15:15:13 UTC
doh - wrong status
Comment 8 Tim Funk 2004-02-07 15:15:56 UTC
fix status to INVALID, not FIXED. (Sorry for the extra emails)
Comment 9 Tim Funk 2004-06-12 16:22:42 UTC
*** Bug 29537 has been marked as a duplicate of this bug. ***
Comment 10 Ephemeris Lappis 2004-06-15 18:21:17 UTC
I've been reading again the servlet 2.3 specification, and, actually, i don't 
see in it anything that give the opposite position, ie always return the 
principal when one has been authenticated, when the requested url is protected 
or not. Further, Tomcat 4 behaves as expected (i mean, i expect), which is, i 
think, the 2.3 implementation. What about the 2.4 version, which is the base 
for the new Tomcat 5 ?...

More, what about the 'isUserInRole' ? Does it follow the same rule ? How a 
simple menu page could take decision according to identity or roles of the 
authenticated user, and show or hide links for example, even if this page 
itself is not protected ?

Thanks for your precisions.
Comment 11 Christian Holm 2004-09-06 23:19:09 UTC
I truly think this is a wrong interpretation of the spec. From the JavaDoc of
HttpServletRequest:

"Returns a java.security.Principal object containing the name of the current
authenticated user. If the user has not been authenticated, the method returns
null."

This clearly states that the getUserPrincipal()-method should only return null
when the user has not been authenticated. There is no exception to this rule, as
earlier comments would suggest.

Clearly it would not be against the spec to always return the principal when
authentication has been done wether or not the viewed resource is protected or
not. This is clearly needed for many web-applications.
Comment 12 Mark Thomas 2004-09-14 22:52:54 UTC
There is a an exception.

<spec-quote>
SRV.12.9 Default Policies
By default, authentication is not needed to access resources. Authentication is
needed for requests for a web resource collection only when specified by the
deployment descriptor.
</spec-quote>

Hence the user is only authenticated for protected resources.

I appreciate the application design issues raised above, but it remains the 
case that TC4 is compliant with the spec.
Comment 13 Alex Paransky 2004-09-15 04:08:00 UTC
That's fine.  You are right, there is no need to Authenticate for non protected
resources, however, once authenticated and the Principal is available it should
be returned regardless if authenticated or un-authenticated resource is being
accessed.
Comment 14 Mark Thomas 2005-09-21 23:49:39 UTC
The underlying issue here is how BASIC authentication works. With BASIC
authentication, the user is required to authenticate with every request and the
browser helpfully caches the user name and password and re-uses them with
subsequent requests.

The spec requires that authentication only takes place if a resource is
protected. Therefore, for an unprotected resource no BASIC authentication takes
place even if the browser sends the credentials. In turn, this means that
getUserPrincipal() will return null since the user has not been authenticated.

One work-around is to use FORM authentication. In this scheme, the user is
authenticated once and the Principal added to the session. This authenticated
Principal remains available whilst the session is valid regardless of whether an
individual request requires authentication.

I have considered modifying the BASIC authentication implementation so a user is
always authenticated if the present credentials but:
- this would violate the spec
- the behaviour if the authentication fails is undefined (because the spec
obviously doesn't define behaviour that violates the spec)

Therefore I am going to resolve this as INVALID since any other behaviour is a
spec violation.

As a final comment I do not like that this means that application behaviour
varies with the authentication scheme specified in web.xml but this is a direct
side-effect of the differences in per-request and per-session authentication.
Comment 15 Alex Paransky 2005-09-22 05:09:25 UTC
(In reply to comment #14)
Unless there have been changes in the latest version of TomCat, I have always
been using Form Authentication.  So the description of this problem is IN the
context of Form Authentication.  Even after being authenticated via a Form
authentication method, the getUserPrincipal still returns NULL for those
resources which are not protected.  

I worked around this problem, however, by creating my own Valve and storing the
authenticated user somewhere I can get it later.  Later, I ensure that this user
is correctly setup for the JBOSS call to the EJB.
Comment 16 Werner Donné 2007-05-25 03:43:34 UTC
I disagree with the assesment of this bug. Tomcat's behaviour is based on the
following:

<spec-quote>
SRV.12.9 Default Policies
By default, authentication is not needed to access resources. Authentication is
needed for requests for a web resource collection only when specified by the
deployment descriptor.
</spec-quote>

Authentication can also be provided when it is not mandated by the deployment
descriptor. The spec quote doesn't say that authentication is forbidden when it
is not specified in the deployment descriptor.

When authentication is provided, no matter how and why, the documentation of the
getUserPrincipal method applies.

The use case for this is any situation where authorisation is based on
application data. When some application logic has found that unauthenticated
access to a resource is not allowed it can require authentication and reconsider
its access control descision. The status code 401 can also be returned by the
application. How would you otherwise implement RFC 3744 for example?
Comment 17 Mark Thomas 2007-06-01 16:42:14 UTC
Having looked at this again, I agree that authenticating the user, if
credentials are present, for a non-protected resource would not be in violation
of the spec. However, the question remains - what to do if that authentication
fails?

Anyway, I am changing this to an enhancement request since the current behaviour
is spec compliant. As ever, patches for review will be welcomed.
Comment 18 Werner Donné 2007-06-02 01:08:52 UTC
When the authentication fails the server can return a 401, because the
spontaneously provided Authorization header is wrong (RFC 2617 section 1.2).
Since the server didn't require authentication for the method, the User Agent
would have volunteered it, perhaps trying to get in and call other methods for
which authentication is required. After having received the 401, the User Agent
can continue interacting with the server unauthenticated. In this scenario the
server should always check a provided Authorization header, even if the method
doesn't require authentication.

Evaluating whether the current behaviour is compliant with the spec or not
depends. The starting point is the specification of the
HttpServletRequest.getUserPrincipal method. Looking at that alone makes the
behaviour non-compliant. Including SRV.12.9 makes it more difficult. Does
SRV.12.9 apply in this case? In don't think so, because it says nothing about
spontaneous authentication, which is allowed.
Comment 19 Werner Donné 2007-09-20 02:47:33 UTC
Created attachment 20857 [details]
Make authentication independent of security constraints

The specification of the getUserPrincipal doesn't say the availability of
principal information depends on the existance of security constraints in the
deployment descriptor.

The patch causes the login configuration to be installed unconditionally and
checks if authentication is provided in the request regardless of the existance
of security constraints. Credentials may have been provided spontaneously or
after a 401 response comming from the servlet.

Note that this is also the way WebLogic behaves.

This patch is for Tomcat 5.5.23 and not Tomcat 4 for which the bug was created.
Comment 20 Mark Thomas 2007-09-20 08:03:26 UTC
Please provide a text version of your patch in diff -u form.

Comment 21 Werner Donné 2007-09-20 08:24:35 UTC
Created attachment 20859 [details]
org.apache.catalina.authenticator.AuthenticatorBase
Comment 22 Werner Donné 2007-09-20 08:25:25 UTC
Created attachment 20860 [details]
org.apache.catalina.startup.ContextConfig
Comment 23 Mark Thomas 2010-12-16 14:00:15 UTC
Having looked at this further this is no need for a patch. Tomcat has the necessary functionality to do this. You just need to ensure that a) the application is using sessions and b) that the authenticators are configured to cache the authenticated Principal in the session.

A recent enhancement to Tomcat 7 (the alwaysUseSession attribute) will make this even easier. On earlier versions, ensure a session exists before the authentication takes place. Depending on circumstances that might require a valve.

Marking this as WONTFIX since the patch isn't going to be applied.

The other advantage of this approach is that the handling of fail unprompted authentications does not need to be considered. There were issues with complying with RFC2617 with that approach and it couldn't possible work with DIGEST auth.
Comment 24 Werner Donné 2010-12-16 14:32:26 UTC
Sessions don't solve the problem and doesn't make it comply to the spec. The container is not the only party that can decide if authentication is necessary. The application can do this too. Even if no credentials were provided spontaneously by the client, the application could set the status code to 401. The client would then reissue the request with credentials and the application couldn't anything else but return 401 again, because the principal is not passed through by Tomcat as the resource is not declared as protected in web.xml.

What were the issues with RFC 2617?

The fact that it wouldn't work for DIGEST authentication is not relevant. We're talking about a valid scenario for Basic authentication.
Comment 25 Mark Thomas 2010-12-16 16:09:04 UTC
This is a grey area of the specification. My reading of the various specs remains that Tomcat is spec compliant. I have added this to my list of things to ask the Servlet EG to clarify in 3.next 

I believe that a web application's fundamental behaviour should not change just by changing the authentication mechanism. That DIGEST can't work with pre-emptive authentication is a significant concern.

The scope of the feature is also important. This is do-able as previously described with container managed authentication. Once the application starts to get involved, things get more complex. However there is a way to do this in Servlet 3.0. The application can call request.authenticate() but it needs to make sure it checks the return code and stops any 401 going back to the client. The application will also need to handle any IllegalStateExcpetions if the response has already been committed.

The RFC2617 issue was mainly that a failed authentication SHOULD result in a 401 response and this feature requires that there is no 401 else the application could end up prevent a user from accessing a page for which no authentication is required. The SHOULD does give some leeway (it isn't a MUST) but I'm not convinced there is a good enough reason to ignore the spec here.
Comment 26 Werner Donné 2010-12-17 04:05:00 UTC
There are two issues here. The first is pre-emptive authentication. RFC 2617 section 1.2 is very clear on this:

" A user agent that wishes to authenticate itself with an origin
   server--usually, but not necessarily, after receiving a 401
   (Unauthorized)--MAY do so by including an Authorization header field
   with the request."

And further:

"If the origin server does not wish to accept the credentials sent
   with a request, it SHOULD return a 401 (Unauthorized) response. The
   response MUST include a WWW-Authenticate header field containing at
   least one (possibly new) challenge applicable to the requested
   resource."

This means the origin server must always deal with this, no matter the technology being used. The fact that a method is not declared as being protected doesn't play any role.

The second issue is that according to you the application should not be able to trigger the challenge-reponse mechanism by setting the status code to 401. Can you give one piece of evidence in the servlet spec for this? Are there restrictions in the HttpServletRequest.setStatus() method that say the application can't use certain status codes? Here is what the servlet spec says about programmatic security:

"Programmatic security is used by security aware applications when declarative security alone is not sufficient to express the security model of the application."

Declarative security is fine, but doesn't prohibit programmatic security and programmatic security doesn't require declarative security to be present. They are simply not related. According to you they are. Can you tell me why?

The patch shows how trivial it is to implement this correctly. Would harm be done if it were implemented? Which spec would be violated in that case?

Your stance makes it impossible to implement WebDAV ACLs (RFC 3744). A user can change access control for a resource from protected to public. The former requires authentication prior to access control, while the latter doesn't.
Comment 27 koma 2010-12-18 06:18:27 UTC
see comment #26
Comment 28 Mark Thomas 2011-03-28 06:11:46 UTC
Since this is the last open Tomcat 4 bug and a) I would like to make the Tomcat 4 bugs read-only and b) any changes as a result of this bug will be in Tomcat 7 onwards, I am moving this issue to Tomcat 7.
Comment 29 Mark Thomas 2011-04-01 07:21:31 UTC
This has been fixed in 7.0.x and will be included in 7.0.12 onwards. It is disable by default but can be enabled on a per context basis.

To address some of the points raised in comment 26:

I don't believe RFC2617 or the Servlet specification are sufficiently clear to enable preemptive authentication by default. They are open to interpretation and my reading of them is that there are ambiguities in the language that defines the server response in such a scenario.

I did not say that an application cannot trigger authentication by returning a 401 response. My point was that if the application uses the Servlet 3.0 API to manually implement preemptive authentication, it needs to consider what to do if that authentication fails when the client has requested a non-protected resource. Returning a 401 in that case strikes me as the wrong thing to do but that goes back to the ambiguity in the Servlet spec and RFC2617.

Regarding programmatic security and declarative security, the relationship between them is set out in section 13 if the servlet spec. The point I was making was that if an application uses both declarative security and programmatic security and the programmatic security performs actions normally handled by declarative security (e.g. sending and processing authentication headers) then you need to be careful to ensure that the two do not interfere.

To summarise the current position:
- with alwaysUseSession and cache enabled on an authenticator, the authenticated user name and principal will be available to all requests once the user has accessed a protected resource
- with preemptiveAuthentication enabled on an authenticator, the authenticated user name and principal will always be available
Comment 30 Christopher Schultz 2011-04-01 17:07:10 UTC
Fascinating reading. My question would be: why does anyone want to have a resource that doesn't need authentication (no security-constraint) but then checks the authentication status, anyway (calls getPrincipal, isUserInRole, etc.)?

If this "bug" was really ruining anyone's day, isn't it a simple matter of providing an aliased URL to the same resource that /is/ protected by a security-constrains and always sending authenticated users to /that/ URL instead?

Glad the 9-year saga is over...
Comment 31 Werner Donné 2011-04-02 04:00:55 UTC
@Mark The relevant specs are crystal clear. If you think there is any room for interpretation then you should provide proof of how your interpretation can be constructed. At present we still don't know what that is.

You wonder what a "non-protected" servlet should do when the provided credentials are wrong. That is simple, it should do nothing, because the container will have returned a 401, which it should always do when the credentials are wrong. That is because there is no response code for reporting wrong credentials.

Where can there be interference? All involved parties, container and servlet, should comply with the specifications. When the container imposes a security constraint because it was declared in the application, the servlet won't see anything about it.

@Chris You don't seem to understand the difference between declarative and programmatic protection. An alternative URL doesn't provide prgrammatic protection. Study the WebDAV ACL specification and you will see it is impossible to implement it without this bug being fixed.