[TransWarp] Authentication services, specialists, and rules-checking
Phillip J. Eby
pje at telecommunity.com
Thu Aug 7 17:30:48 EDT 2003
I've been working the last day or two on authentication services for
peak.web, and have run into some interesting hitches. The most basic
functions of an authentication service are easily met, you just need
something that pulls data out of a request and looks up a user, no real
problem.
However, although identifying the current user is a straightforward
computation-only task, there is a wide assortment of related services that
are also needed. For example, a login form, and a logout method. There
may also be needs to manage users, register a new member and so on.
In principle, these needs are part of the application, and should be
defined as part of the application's presentation components. But, it may
be that the authentication service needs to access them. For example,
maybe the right behavior is for the authentication service to present the
login screen when there's invalid authentication data found.
As I think this over, I'm realizing that this isn't quite right. The
authentication service can just raise an error, which can then be adapted
to whatever behavior is desired by the application. And, different
services might have different errors that they raise. For example, an auth
service with policies like those of Yahoo, might raise a "your login has
timed out" error, which would then be adapted to a "please re-enter your
password" screen. (Technically, it probably wouldn't work this way, and
thinking about how to do it correctly actually leads to some other
interesting thoughts I'll explore later in this message.)
For other operations, like logging a user out, signalling credential
changes and such, it's more than adequate to use the convenient, centrally
located authentication service. However, for services that a user needs to
access via the web, the authentication service can't be used. So, we end
up needing a "user specialist" component that has a known URL within the
site, in order to have e.g. a '/logout' method.
This isn't such a big deal, but it does make me wonder then, how other
components in the site will find it. For most of our current applications,
we just use '/acl_users' or some such, per Zope, and it's a hardcoded
path. But I've been saying for some time now that hardcoded paths make for
poor modularity. It seems to me that there should be some way to access
specialists, in the same way that we can DMs. Thus, presentation
components would be more reusable between sites. To do this, a Decorator
would just bind to a Specialist, and the Specialist would have to be
available from some parent Decorator. In a template, one can refer to the
specialist relative to the current decorator, and use a "url" DOMlet to
point it to its "true" URL, so long as the Specialist is a "Resource" in
our current terminology. Thus, we need service-oriented Decorator classes
that know how to ensure their absolute URL-ness, even if accessed via
another object.
Backing up a little bit to the error handling thing, I'm thinking that
probably authentication services should be configurable as to what errors
they use, and/or that the NotAllowed error may need to be more
fine-grained. Right now, NotAllowed just means that you didn't have the
access needed to do what you wanted to do. But, there is no way to
parameterize that, to say that you're not allowed because for example, your
session expired, versus, you're just not allowed, period.
At issue here is the fact that interaction.allows() communicates only via a
true-or-false return value, so there's no way to get the details, even if
the business rules that interpret the permissions know exactly what's going
on. Further, the current traversal machinery just uses NOT_FOUND and
NOT_ALLOWED as sentinel values, so there's no data there, either.
So I wonder: should rules raise errors, instead of just a yes/no
response? Certainly, this would allow them to communicate in very
application-specific terms about the nature of the issue. If part of the
required interface for such errors was to provide a method that would
provide alternate data for templates trying to access the protected data,
then we could perhaps allow DOMlets to catch these errors and render the
alternate content, similar to certain modules in My Yahoo displaying "you
must log in to access this feature" when you haven't entered your password
in a while, and meanwhile the news modules and such would still display
their (public) contents.
Granted, I do have some qualms about throwing exceptions for this. For
example, if more than one permission guards an object, how do you determine
which exception should be used/displayed? I suppose another useful
approach would be to have interaction.allows() return the failed "attempt"
object, which would have a false value, but contain a log of information
about why the attempt failed. OTOH, this seems like maybe *too much*
information, especially if you're paranoid about security. (Of course, I
suppose if you're paranoid, you won't put that kind of information
disclosure into your security rules.)
Hm. As I think about how we might try to deal with such "logs of failing
things", I begin to think two things: 1) maybe exceptions would be better,
and 2) we could simply require that permissions always be
singular. Instead of saying permissions=[Worker,Manager], we might want to
say permission=WorkerOrManager, and encapsulate the "or" within the
permission rule. Then, it could say, e.g. "You must be a worker or manager
within this facility", instead of issuing two errors for, "You must be a
worker" and "You must be a manager", which makes little sense to the
user. Meanwhile, permissions that "and" two other permissions can simply
return the result from the permission that failed first.
I still don't really want interaction.allows() to throw an error, though,
all things considered. It would probably make more sense for
'contextFor()' or 'traverseTo()' to throw the error. The idea here is that
we do want an error, so that code using a traversal knows that the
traversal it's on is broken. It doesn't really make sense to return a
valid new traversal context that simply points to a special "not found" or
"not allowed" place, because then you might have a template that keeps on
going and just coincidentally appears to work.
Incidentally, this actually is better for error handling in templates,
because it can be made explicit. Right now, DOMlets just (explicitly)
ignore missing or unauthorized data. It would be better to let them
implicitly ignore it, but also be able to explicitly handle it, e.g. by
letting you specify the granularity at which a set of items is
required. For example, if you lack access to one field of the record,
should you be denied access to just the field, or the entire record? Maybe
the whole list of records? Being able to handle the error at the level of
some useful block structure is better for things like the Yahoo "you need
to login to use this module", as it makes no sense for the module to write
this once for every record it would have shown!
In order to do this, of course, there would need to be an explicit
"catcher" DOMlet specified at some level. The catcher would supply a new
state to its children, that wrote their output to a temporary buffer. In
case of an error, the catcher would attempt to adapt the error to something
that could be displayed in place of the original contents, and write that
instead. If there was no error, it would write the buffered output to its
parent stream.
So, for something like My Yahoo, each module's content block would be
wrapped with a catcher that would revert to giving an error message as soon
as a contained, non-error-handling DOMlet tried to access the protected
content. But, if all the content was accessible, it would be written out
and processing would proceed.
So, to go back and try to summarize...
* We need "absolute location" decorator classes for services like Specialists
* There needs to be some kind of security.Denial("message") object that has
a false value but can also be converted to a string/unicode value, and
maybe can be raised as an exception.
* permissionsNeeded should become permissionNeeded, throughout all of
PEAK. Permissions like "ThisOrThat", can be implemented by rules that
simply return 'attempt.allows(This) or attempt.allows(That) or
security.Denial("You need this or that").
* IWebTraversable.traverseTo() should accept a context rather than an
interaction, and throw NotAllowed(context, denial) instead of returning
NOT_ALLOWED, when an access attempt is denied. Similarly, it should throw
NotFound(context) when something isn't found. Special handling for
NOT_FOUND and NOT_ALLOWED should just go away altogether. This
unfortunately means that certain traversables such as Decorator and
MultiTraverser will now need some extra exception handling to accomplish
their tasks, as will interaction.getDefaultTraversal. But, I think this
will save a lot of duplicated effort in DOMlet classes, and I think there
will be a lot more custom DOMlet classes than there will be custom
traversables that need special treatment for these not allowed/not found
conditions.
* There needs to be some kind of "catch" DOMlet, although I don't know what
it should be called. Maybe "try"? There's also something of a question as
to what its default handling for errors should be. I suppose we could have
a 'templateErrorProtocol' on the interaction, to which the error instance
is adapted, but then I wonder whether an individual DOMlet might want
specific configuration for this. Perhaps we should just make the target
protocol a binding on the DOMlet, and let people with custom needs subclass
it. That might be simplest. Indeed, perhaps we could just make
IWebException include a method for being rendered *within* a template, as
opposed to *instead of* a template. That would let you specify both
behaviors for an error in one place.
Anyway, as always, your thoughts, questions, suggestions, comments, etc.
are welcome.
More information about the PEAK
mailing list