Authentication and authorization in Grok¶
Author: | Jan-Wijbrand Kolman (j-w) |
---|---|
Version: | This document is based on Grok-1.2.x |
This document is an attempt to explain, into as much detail as needed, how the authentication and authorisation process is handled in Grok. This explanation is based on Grok-1.2 and the specific versions of its dependencies.
Note
This document most probably contains errors, ambiguities or ommisions. The reader is encouraged to discuss this document on the grok-dev mailing list and the #grok IRC channel.
Authentication versus authorisation¶
It is important to make a clear distinction between the terms “authentication” and “authorisation”. To a large extent, the Wikipedia definitions apply:
- Authentication
- Authentication (..) is the act of establishing or confirming something (or someone) as authentic, that is, that claims made by or about the subject are true(..) This might involve confirming the identity of a person(..).
- Authorization
- Authorization (also spelt Authorisation) is the function of specifying access rights to resources, which is related to information security and computer security in general and to access control in particular. More formally, “to authorize” is to define access policy(..)
Authentication¶
Grok - as it is built on top of the Zope Toolkit - has a particular name for the “actor” or “user” that is acting upon the application by issueing a request. This “actor” is called the principal.
The request object in Grok has a reference to an object that provides IPrincipal - the so called principal object. In these cases where authenticating the principal is not possible, the principal object will provide IUnauthenticatedPrincipal.
The principal is associated to the request object by several authentication attempts during the handling of a request. In other words, Grok tries to authenticate each and every request.
This is worth repeating: Grok will try to authenticate the principal for each and every request!
The authentication follows several steps:
Before traversal hook¶
At the very beginning of the request handling, just before object
traversal is started, the publisher will call beforeTraversal()
on
the publication object (see zope.publisher.publish and
zope.app.publication.zopepublication). This beforeTraversal()
implementation will try to authenticate against the global
IAuthentication utility.
By default, the global IAuthentication utility implementation that
is registered in a Grok application, is that of the
zope.principalregistry. It knows how to authenticate the current
request using information from the application’s
parts/etc/site.zcml
.
When this “top-level” authentication is succesful, no other authentication attempts are made!
Traversal¶
The next step in the request handling is to traverse step by step to the requested object. For each step in the traversal, a callTraversalHook() is called on the publication object. In the callTraversalHook() implementation of this hook, an authentication attempt is undertaken - at least, as long as the object currently being “traversed over” is an ISite and actually has a IAuthentication utility registered for and the current principal on the request still provides IUnauthenticatedPrincipal!.
In other words, the very first authentication attempt that succeeds, wins!
After traversal¶
After the traversal has completed (mind you, the retrieved object is not called or “rendered” just yet!), the afterTraversal() hook is called on the publication object. The afterTraversal() implementation will do yet another authentication attempt.
If authentication has not succeeded thusfar, the request object will still have the unauthenticated principal associated. If authentication would have been succesful, the request will have and object associated providing IPrincipal. The implementation for this IPrincipal depends on the IAuthentication utility that did the authentication.
IAuthentication utilities¶
As we have seen, in each request there are several attempts to
authenticate the request. This is done by calling authenticate()
on the found utility. If the utility returns None
Grok will
continue with the IUnauthenticatedPrincipal, if it returns an
IPrincipal object that object is used for the request.
In a Grok application, there’s at least a globally registered
IAuthentication utility available, implemented in
zope.principalregistry. Its authenticate()
method will validate
a loginname and password “pair” - also called the credentials -
against a simple registry.
Actually retrieving the credentials however is delegated by this principal registry to the ILoginPassword adapter for the request. The ILoginPassword implementation for this adapter that is registered by default, will try to get Basic Authentication credentials from the request.
So, as long as the correct Basic Authentication credentials are available in the request, the principal will be authenticated.
Note
It is important to realize that other IAuthentication
implementations might have a completely different strategy
for authenticating requests. Later we will see how the often
used PluggableAuthentication implements the
authenticate()
contract.
Authorization¶
User agents, like web browsers, do not by default provide Basic Authentication credentials in the requests they send to the application. They have to be triggered in doing so. Basic Authentication is triggered by a 401 status code in the response. But what mechanism will make sure a 401 status code is set on the response object in a Grok application?
Let’s assume we have view that is publicly accessible. Let’s also assume that we have a request for this view, that cannot be authenticated. The security mechanism will check whether the current request is allowed to access this particular view (exactly how this is checked is out of scope for this document. It would deserve an article on its own).
Since it is a public view, the security checking will allow it and the publisher will make sure the view is rendered.
Now suppose we have an unauthenticated request for another view, a view that is protected. Since we were not able to authenticate the request, we cannot determine whether the request should be allowed and the security mechanism will raise an Unauthorized exception.
Whenever an exception is raised in the publication process, the publisher will try to handle the situation in a clean manner by giving the publication the possibilty to do something about the error situation by calling the handleException() hook.
Part of the important house-keeping that is performed in the handleException() hook, is the lookup of a view for the current exception and having this “error” view rendered.
Grok has “error” views registered for several error situations, including a view that is specifically built to handle Unauthorized exceptions. This particular error view implementation will call unauthorized() on the request object which will set the 401 status code on the response, in turn triggering a login/password dialog of some sort in the user agent and that is the end of this request’s life-cycle.
This implies that the error view is responsible for somehow triggering the authentication challenge. If there is no such view, the result will just be a basic error message displayed, with not much more that the name of the current exception.
A new request could come in, now with credentials in the form of Basic Authentication, and the the new request cycle will follow the described process for authenticating the request. In the example of a protected view, we will now have an authenticated request and we can check whether the now-know principal actually was granted sufficient permissions to access this resource. (XXX refer to the documents about defining permissions and roles and how to restrict access on views).
Other implementations: the Pluggable Authentication Utility¶
Even though it is the default setup, actual real-world applications hardly ever use Basic Authentication for authenticating the request or the global principal registry for user management. Basic Authentication has fundamental security flaws (especially when using unencrypted connections) and so does the user experience.
One of the most often used alternative IAuthentication implementations is the flexible, but sometimes confusing, Pluggable Authentication Utility (PAU). This utility lives in the zope.pluggableauth package.
Note
This section will not have detailed explanation about installing the PAU and its plugins. This is left for a how to-type document.
Authenticating the request¶
As we have seen, Grok will attempt to authenticate each request by looking up an IAuthentication utility during the traversal. Suppose we have an Application object installed and an instance of the PAU registered for it.
The PAU then, will be used to authenticate the request, when traversed to this application. Its authenticate() implementation splits the authentication attempt in two steps (and plug-in points):
- Extract credentials for the request
- Validate the credentials
Extracting credentials¶
Credentials extraction is handled by a plugin point. Utilities that
provide ICredentialsPlugin can be registered for this plugin
point. The PAU is then configured to use these plugins, in a given
order, in order to obtain credentials. The PAU will call
extractCredentials()
on each of the registered plugins and the first
plugin that return a login-password pair “wins”.
For this section we will look at one specific, often used, credentials extraction implementation, called SessionCredentials.
Session Credentials plugin¶
The SessionCredentials plugin will try to extract credentials for
the request by first looking for two specific keys in the form
variables of the request, namely login
and password
. When
values are present in the request for these keys, the values are
stored in the session object.
Then, an attempt is made to get the credentials from the session
object as they might have been stored just now, or have been stored
already there in a previous request. If the user’s session object
indeed contains credentials, these are handed back to the PAU. The
PAU then continues with the second step, validating the login-password
pair. If no credentials have been found, None
is returned.
If None
is returned, the PAU will try again in the next registered
ICredentialsPlugin. If there are no more ICredentialsPlugin
components registered for this PAU, the process ends, and the request
will continue with an IUnauthenticatedPrincipal
object for its
associated principal.
Validating the extracted credentials¶
Now that the Pluggable Authentication Utility asked its credentials
plugin for a login and password, the PAU needs to verify the validity
of the login and password. Like for the ICredentialsPlugin
components, the PAU will now ask each of the IAuthenticatorPlugin
components that have been registered to verify the
credentials. These components are supposed to either return an
IPrincipal
object, or None
. In case no principal is returned,
this cycle is repeated for the next ICredentialsPlugin
component
that has been registered until the list of registered components has
been exhausted.
Valid credentials thus will result in an authenticated principal associated to the request. In words, the PAU now is “done”, and no other authentication attempts are being made for this request during the remaining part of the traversal, a view is looked up and - if the security mechanism allow for - rendered.
Not authorized, issueing an authentication challenge¶
Suppose we have an unauthenticated request for a protected
resource. As the security machinery cannot determine whether this
request is allowed to access the resource, an Unauthorized
exception is raised and the the publication object will attempt to
render an error view for this exception. We have seen that the default
view for the Unauthorized
exception will call unauthorized()
on the request object. This would trigger the 401 status code on the
response and the user agent would display some sort of dialog asking
the user for Basic Authentication credentials.
That is however not necessarily the correct response, as components
registered in the PAU probably require a different method of
retrieving credentials! Instead of calling unauthorized()
on the
request, error views should call the unauthorized()
method on
nearest IAuthentication
utility.
Note
Even though the default error view to handle Unauthorized
exception will call the request.unauthorized()
method,
it is probably preferrable to call the
getUtility(IAuthentication).unauthorized()
from error
views.
This means, a project that wishes to use the Pluggable
Authentication components effecitively needs to create and
register a custom view for the Unauthorized
exception.
Attention
The Grok project might want to consider implementing a set of baseclasses for common error view, including one for ``Unauthorized`` and have it call the latter. This would unify the API for triggering the authentication challenge.
In fact the globally registered IAuthentication
utility (that of the
zope.principalregistry) implements the unauthorized()
method as
well, and will, by delegating to the ILoginPassword
adaptation of
the request, trigger the 401 response status code.
The PAU implements the unauthorized()
method again in a pluggable
manner. It will, like for the authenticate()
part of the
IAuthentication
contract, delegate the actual challenge() to its
ICredentialsPlugin
components.
Challenge in the SessionCredentials plugin¶
The SessionCredentials plugin implements the challenge essentially
as a redirect to a login page. The redirect URL will - if it can be
computed - include a “camefrom” URL as a parameter. The login page
should be a view containing an HTML form. It should have at least have
two form fields, identified by the names login
and password
.
The form submit action should POST
the form to the login page
itself. Since Grok will try to authenticate each and every request,
the POST
request to the login form itself will also be
authenticated. And as we have seen, the credentials extraction of the
SessionCredentials plugin, will try to find values for login
and
password
keys in the request. And this very request will have
these values, as we justed “POST-ed” them!
The login page thus now has an authenticated principal available on
the request and can decide to, in turn, redirect to the camefrom
URL, closing the challenge-authenticate-view cycle.
How principals are created¶
...