Sessions and Authentication for Web2
Hello. I am in the middle of porting my application(s) to web2, and it seems that general authentication and session handling is not yet implemented. My requirement is rather simple: 1. When the user connects to my system (via HTTPS), I check for a cookie that indicates their server-side session. If they have a session active, I touch() the session so that the expiration date is pushed out. 2. If the cookie does not exist, or the session has expired, then I send them through one of 3 authentication mechanisms depending on either their IP, a query argument, or what resource they are trying to access. a) If they are coming from a YALE IP address and have not explicitly asked for HTTP Digest authentication, I use the CAS authentication mechanism: http://www.yale.edu/tp/auth/cas20.html b) Else, if they are viewing a limited "semi-restricted" set of resources, I use another custom challenge-response mechanism that is similar to CAS (but slightly different) c) Otherwise, I use HTTP Digest Authenitication. All of these methods require that the request be cut short, and either a 401 is returned, or a HTTP Redirect (for CAS). 3. Once I have an authorized user; I create a session for them, and set a 20 minute timeout. I might have multiple requests active for the same session. Once I have a session, I need a few things: 1. A way to getSession() from a given request; in particular, most resource authorization is based on the user's identity. 2. Request specific storage of temporary working data so that requests in-progress can accumulate results before the response is generated. 3. Session specific storage, so that information about the user's context can be maintained. This needs to be synronized with a database server since the Session may be shared across multiple servers. 4. A JobQueue for that particular Session. Many queries take a long time to run; and incremental results and/or failure notices need to be managed between each request. Of these requirements, #1 is most important, and #4 is a wish-list item for the next version of my application. We had a chat on IRC today, and a few random notes/ideas emerged: a) There was a strong preference to keep Session and Authentication completely separate. Currently Nevow's Guard mixes the two and thus is overly complicated and non-modular. Nevow also uses Componentized, which is depreciated (in favor of... ?) http://divmod.org/trac/browser/trunk/Nevow/nevow/guard.py http://divmod.org/trac/browser/import/Nevow/sandbox/mg/guarded/guard.py b) Besides Nevow's GuardSession, there are a few other session implementations besides the older twisted.web implementation. A requtil.py in web2 seems to be a straight-forward port of tw.web's sessions. Quixote also has a session manager, http://quixote.idyll.org/session2/ which is licensed to steal. c) Exarkun expressed a strong (ok, mandatory) preference for the use of tw.cred in any Authentication solution. However, it was noted that tw.cred does not allow for challenge-response authentication mechanisms (which all of mine are). Specific examples were noted: twisted.protocols.sip, SASL, OTP d) There was talk that the Session manager should actually be a filter on the Response object; so that it can rewite paths and/or inject cookies as needed before the response is sent. This seems a bit complicated, no? e) I can probalby help in some ways, but I'm not a Twisted expert. I could continue to collect requirements and post; but I have short term deadlines (as usual). If you wish I could put two files in twisted.web2, marked 'experimental': session.py This would contain session manager, how ever it is eventually implemented. guard.py This would contain the gule to link tw.cred to web2. I prefer an iterative; implement the simplest solution first, and then grow it as needed approach (with clear marks on instability). I wouldn't mind owning these two files and collecting feedback, etc. Anyway, thanks for listening. Any thoughts as to how to best proceed would be very welcome. Kind Regards, Clark P.S. For now, I'm hacking my own session and auth code. There is a requtil.py in web2 which is a stab at sessions, and both David Reid and I have HTTP Auth code in our sandbox.
On Wed, 16 Nov 2005 13:48:12 -0500, "Clark C. Evans" <cce@clarkevans.com> wrote:
We had a chat on IRC today, and a few random notes/ideas emerged:
a) There was a strong preference to keep Session and Authentication completely separate. Currently Nevow's Guard mixes the two and thus is overly complicated and non-modular. Nevow also uses Componentized, which is depreciated (in favor of... ?)
http://divmod.org/trac/browser/trunk/Nevow/nevow/guard.py http://divmod.org/trac/browser/import/Nevow/sandbox/mg/guarded/guard.py
I'm not sure what "completely separate" means in this context. I don't think I agree, although I'm not sure about the specifics of the implementation. I do think that there should be no public session-management API divorced from authentication though. The use of getSession() is a mistake in every single case where it's used, except those in guard itself. What resource you're viewing should implicitly represent what session you are talking to. The whole idea of a session is entirely internal state that should not be exposed to the user. What it amounts to is a rewrite of Python's argument-passing convention, used to pass stuff in an implicit, out-of-band way that's hard to document and impossible to test for. All the discussion of Componentized and Context and all these other objects that are pretty much just different names for a free-form dictionary are indications that session-management == encapsulation violation, if it's exposed to the user. Every session is associated with a user. The representation of the session is the top of the resource tree. If you need to persist information about the session, your resource tree should be pointed at a persistence system of some kind. Perhaps the session manager should have better/more explicit support for simultaneous distinct sessions; in the case of anonymous sessions that would certainly be helpful. As JP stipulated, this all *must* go through cred. A separate session interface is unacceptable. It is already hard enough managing the insanity of HTTP in a multi-protocol server. I am aware that this is not how "most" appservers do things. However, "most" appservers also spawn hundreds of threads, and operate by putting conditional structures in a special-purpose scripting language into templates. Add "exposed session interface" to the list of common design mistakes :). All that said, I don't think that your requirements are wrong, and I do think that the implementation specifics of the Nevow session initiation implementation are pretty bad, and they are mostly my fault.
c) Exarkun expressed a strong (ok, mandatory) preference for the use of tw.cred in any Authentication solution. However, it was noted that tw.cred does not allow for challenge-response authentication mechanisms (which all of mine are). Specific examples were noted: twisted.protocols.sip, SASL, OTP
Hmm. How did you get this idea? Cred's design was specifically to facilitate challenge-response authentication. That's why login() takes credentials and returns a Deferred. The assumption is that the credentials object will encapsulate whatever facets of the user's connection are required to do the negotiation process. Again, this may be a place that Nevow's current session-initialization strategy falls down; IIRC it wouldn't allow for that Deferred to be fired by multiple different web-page hits, since it is a Deferred which is expected to itself return a Resource. That's a limitation of guard though, not cred.
d) There was talk that the Session manager should actually be a filter on the Response object; so that it can rewite paths and/or inject cookies as needed before the response is sent. This seems a bit complicated, no?
Seems so to me. Then again, the implementation specifics of the session manager are often pretty horrible. This is another major reason that I think that the API for sessions should be kept as far away from the user as possible.
e) I can probalby help in some ways, but I'm not a Twisted expert. I could continue to collect requirements and post; but I have short term deadlines (as usual). If you wish I could put two files in twisted.web2, marked 'experimental':
session.py This would contain session manager, how ever it is eventually implemented.
guard.py This would contain the gule to link tw.cred to web2.
web2 is itself experimental, and you are taking a risk by doing work with short-term deadlines on something unfinished. Keep in mind that if these files are determined to be a bad idea later, they may be removed without warning and without regard for your project or deadlines. You may want to work in a branch to provide yourself (and your employer) with some guarantee of stability.
On Thu, Nov 17, 2005 at 06:47:27AM -0500, glyph@divmod.com wrote:
I'm not sure what "completely separate" means in this context. I don't think I agree, although I'm not sure about the specifics of the implementation. I do think that there should be no public session-management API divorced from authentication though.
The point of separation is: web services don't need session management but they might require authentication. And session management can be required without requiring authentication. Having separate filters for session management and authentication should be possible without many problems using filters. Currently the filter signature is the following (none of the below functions exist yet): def sessionfilter(req, res, ctx): # do session handling here def authenticationfilter(req, res, ctx): # do authentication handling here Then setting these as explicit filters in the interface will allow to have separate session and authentication handling without even having them know of each other existence, which is a good improvement IMHO. The logic I proposed for the session management is the following: - Request arrives at the server - sessionfilter looks for cookie/urlsegment/whatever. - there is a cookie, search for the corresponding session in the sessionstorage. - there is a session then: retrieve the session from the session storage (this returns a deferred and then adds as callback the rendering of the page). - there is no session then: create a new 'proxy' session and make the rendering buffered. - there is no cookie information then: create a new 'proxy' session and make the rendering buffered. - the page is rendered (and the user data in the session might be modified in some way). - if the session data was modified: - if the session is a proxy object: during the first modification it will create a local session object and marks it as touched. - if the session is a real session object: mark it as touched. - if the session data was not modified: ignore - very end: if the session was touched commit the changes back in the session storage. There might be inconsistencies in the above flowgraph of course, I wrote this down without thinking too much about current APIs or limitations. It might be too much complicated or whatever, the only purpose is to discuss how to do this :). The authenticator logic is similar: - request arrives: gather credentials from the request - call portal.login() - if the credentials are not met return a FORBIDDEN responsecode and blabla - if the credentials are met: return the adapted resource returned by portal and start the locateChild on the remaining segments on that resource (? judging from the current API of the filters this can be hard though to implement in this way, since the filters are called after the resource has been located IIRC).
As JP stipulated, this all *must* go through cred. A separate session interface is unacceptable. It is already hard enough managing the insanity of HTTP in a multi-protocol server.
Indeed. Totally agreed.
All that said, I don't think that your requirements are wrong, and I do think that the implementation specifics of the Nevow session initiation implementation are pretty bad, and they are mostly my fault.
We are trying to find a new way to do things without boundaries to old APIs and such :). So many changes have to be made that discussing with backwards compatibility in mind is just a waste of time IMHO. It is good if we could just brainstorm some stuff and bash each other. :)
Again, this may be a place that Nevow's current session-initialization strategy falls down; IIRC it wouldn't allow for that Deferred to be fired by multiple different web-page hits, since it is a Deferred which is expected to itself return a Resource. That's a limitation of guard though, not cred.
This is interesting to integrate with the filters idea then and might be useful (given the problem identified above). Hope everything makes some sort of sense. -- Valentino Volonghi aka Dialtone Now Running MacOSX 10.4 Blog: http://vvolonghi.blogspot.com http://weever.berlios.de
On Thu, Nov 17, 2005 at 01:17:11PM +0100, Valentino Volonghi aka Dialtone: | Currently the filter signature is the following (none of the below | functions exist yet): | | def sessionfilter(req, res, ctx): | # do session handling here | | def authenticationfilter(req, res, ctx): | # do authentication handling here | 1. How do I know what the response is before I even authenticate or create a session? 2. Which comes first, I assume session then authentication. 3. These need to come _before_ any of my other resources get the request. It seems that the dispatch mechansim might need a way to "register" that a filter should be applied at the end of processing; so that once the Response is generated, it can be wrapped on the way out. | We are trying to find a new way to do things without boundaries to old APIs | and such :). So many changes have to be made that discussing with backwards | compatibility in mind is just a waste of time IMHO. It is good if we could | just brainstorm some stuff and bash each other. :) Web2 is already substantially different from Web: don't stop now! Clark
On Thu, Nov 17, 2005 at 04:29:36PM -0500, Clark C. Evans wrote:
| def sessionfilter(req, res, ctx): | # do session handling here | | def authenticationfilter(req, res, ctx): | # do authentication handling here |
1. How do I know what the response is before I even authenticate or create a session?
In order to start writing the response to the outside you have to call response.startProducing() So you can 'easily' avoid transmitting anything but keep producing the page with a BufferedStream that can consume but not produce until you tell it to. In this way the environment will change without any output and then you will be able to see the changes at the end and decide consequently.
2. Which comes first, I assume session then authentication.
Easily changeable by changing the order of Request.responseFilters
3. These need to come _before_ any of my other resources get the request.
I don't understand the implication of this. Can you elaborate a bit? :)
It seems that the dispatch mechansim might need a way to "register" that a filter should be applied at the end of processing; so that once the Response is generated, it can be wrapped on the way out.
There already is: Request.addResponseFilter(f, atEnd=False) -- Valentino Volonghi aka Dialtone Now Running MacOSX 10.4 Blog: http://vvolonghi.blogspot.com http://weever.berlios.de
On Nov 17, 2005, at 3:47 AM, glyph@divmod.com wrote:
On Wed, 16 Nov 2005 13:48:12 -0500, "Clark C. Evans" <cce@clarkevans.com> wrote:
c) Exarkun expressed a strong (ok, mandatory) preference for the use of tw.cred in any Authentication solution. However, it was noted that tw.cred does not allow for challenge-response authentication mechanisms (which all of mine are). Specific examples were noted: twisted.protocols.sip, SASL, OTP
Hmm. How did you get this idea? Cred's design was specifically to facilitate challenge-response authentication. That's why login() takes credentials and returns a Deferred. The assumption is that the credentials object will encapsulate whatever facets of the user's connection are required to do the negotiation process.
While it might be a valid assumption, there is no common public interface provided to facilitate it. Perhaps because no one knows what that should look like, perhaps because no one felt it was necessary. But I do believe that something like twisted.protocols.sip.IAuthorizer, that allows for an arbitrary number of round trips should be in cred, either the ICredentials interface should be extended (probably through a subclass) or a new interface should be created. I'm toying with some ideas of how to best do this, but I don't really "get" cred so if you have any requirements other than arbitrary number of round trips, let me know so i can take those into account now rather than later. -David
On Thu, 17 Nov 2005 11:09:42 -0800, David Reid <dreid@dreid.org> wrote:
While it might be a valid assumption, there is no common public interface provided to facilitate it.
The interface is ICredentialsChecker, specifically, requestAvatarID.
Perhaps because no one knows what that should look like, perhaps because no one felt it was necessary.
Return a Deferred from requestAvatarID...?
But I do believe that something like twisted.protocols.sip.IAuthorizer, that allows for an arbitrary number of round trips should be in cred,
That's not what IAuthorizer does. That's what Deferreds do: you can call deferred-returning methods from within requestAvatarID. That allows for an arbitrary number of round trips. I really don't see how IAuthorizer is relevant to this discussion at all, in fact.
either the ICredentials interface should be extended (probably through a subclass) or a new interface should be created.
Clearly, but that doesn't mean that something should be in cred. From ICredentials' documentation: """ Implementors _must_ specify which sub-interfaces of ICredentials to which it conforms, using zope.interface.implements(). """ ICredentials is explicitly listed as a super-interface, it doesn't mean anything by itself, you have to create subinterfaces for every different kind of authorization.
I'm toying with some ideas of how to best do this, but I don't really "get" cred
Clearly not :)
so if you have any requirements other than arbitrary number of round trips, let me know so i can take those into account now rather than later.
Honestly, I have no idea what you're talking about. Have you read Abe's new book? He covers everything in terms of cred.
On Thu, 2005-11-17 at 21:47 -0500, glyph@divmod.com wrote:
On Thu, 17 Nov 2005 11:09:42 -0800, David Reid <dreid@dreid.org> wrote:
While it might be a valid assumption, there is no common public interface provided to facilitate it.
The interface is ICredentialsChecker, specifically, requestAvatarID.
I don't see that working for things like HTTP Basic and Digest auth, even if ICredentials.checkPassword, returns a deferred, which fires after the last step has been completed, most ICredentialsCheckers do things with the credentials before they even call checkPassword, like check the username exists. But in Basic and Digest auth you don't have the username until you get the response to your challenge. So this is where IAuthorizer comes in it handles all the steps prior to having something that you can use to build a credentials. So if IAuthorizer has nothing to do with this discussion, where would you generate your challenge, and parse the response? You could implement your own ICredentialsChecker and actually do these things in requestAvatarId, via some interface on the credentials, but I know you can't be suggesting that is the right way to do it, because that would break all the modularity of cred.
Honestly, I have no idea what you're talking about. Have you read Abe's new book? He covers everything in terms of cred.
Clearly I'm not explaining myself very well, but yes, the only things I've ever read about cred are the source code and Abe's book. But then I looked at the actual HTTP AUTH implementations, and I feel there is something more needed to support this properly. I'm also wondering where exarkun is, because this extension to cred was his idea in the first place. -David
David Reid wrote:
But in Basic and Digest auth you don't have the username until you get the response to your challenge. So this is where IAuthorizer comes in it handles all the steps prior to having something that you can use to build a credentials.
I'm no cred expert, and I dislike it conceptually, but as far as I can tell it's got all the facilities it needs. HTTP is slightly more complex so I'll start with a SASL-ised imap-like example, to see if I've got the right idea: class simpleproto(basic.LineReceiver): def lineReceived(self, line): if self.creds: # forward to CR module that's in progress self.creds.lineReceived(line) else: cmd, rest = line.split(' ', 1) fn = getattr(self, 'do_'+cmd, None) if not fn: self.transport.write('NO unknown command %s\n' % (cmd,)) self.transport.loseConnection() def do_AUTHENTICATE(self, mechanism): if mechanism=='GSSAPI': self.creds = ChallengeResponse(self) return self.portal.login(self.creds).addCallbacks(self.authok, self.authfail) def authok(self, avatar): self.avatar = avatar self.lineReceived = avatar.lineReceived def authfail(self, why): self.transport.write(why.getErrorMessage()+'\n') self.transport.loseConnection() class ChallengeResponse: def __init__(self, conn): self.conn = conn self.started = False self.context = hypotheticalGssapiModule.init() def lineReceived(self, line): done, response = self.context.input(line.decode('base64')) self.conn.transport.write(response.encode('base64')+'\n') if done: if self.context.success(): self.finish.callback(self.context.getPrincipal()) else: self.finish.errback(self.context.errMsg()) class GssapiChecker: implements(checkers.ICredentialsChecker) def requestAvatarId(self, creds): creds.finish = defer.Deferred() return creds.finish The neat thing about cred is that the code above (if it worked at all, which it would with only slight fiddling) doesn't require the portal or realm to change. Similarly, only a bit of fiddling permits multi-method auth e.g. for IMAP brokenness: def do_LOGIN(self, data): user, pass = data.split(' ', 1) creds = credentials.UsernamePassword(user, pass) return self.portal.login(creds).addCallbacks(...) ...and add an appropriate checker to the portal. HTTP is a bit of a pain because of the "connectionless" basis. The RFCs for the hacky mechanisms like "Negotiate" (the MS-ism for kerberos over HTTP) and such show that. Digest would want some kind of stateless version - you're effectively authenticating requests as opposed to the connection, but the basic principle is the same. I'm sure you know all this. So am I missing something? It looks like cred needs no extending?
On Sat, 19 Nov 2005 23:00:31 +0000, Phil Mayers <p.mayers@imperial.ac.uk> wrote:
David Reid wrote:
But in Basic and Digest auth you don't have the username until you get the response to your challenge. So this is where IAuthorizer comes in it handles all the steps prior to having something that you can use to build a credentials.
I'm no cred expert, and I dislike it conceptually, but as far as I can tell it's got all the facilities it needs. HTTP is slightly more complex so I'll start with a SASL-ised imap-like example, to see if I've got the right idea:
[snip - cool example protocol with cred integration]
HTTP is a bit of a pain because of the "connectionless" basis. The RFCs for the hacky mechanisms like "Negotiate" (the MS-ism for kerberos over HTTP) and such show that. Digest would want some kind of stateless version - you're effectively authenticating requests as opposed to the connection, but the basic principle is the same. I'm sure you know all this.
So am I missing something? It looks like cred needs no extending?
Nope. You're dead on. Cred can do everything necessary to handle Digest auth as-is. Digest auth isn't even the most complex scheme it supports. I wrote the attached quick example of authentication that involves repeated challenges and per-user private authentication-required state. It is mainly the same as your example, with the addition of support for a kind of credential that requires cooperation from both the authentication database and the protocol. Jean-Paul
David Reid wrote:
c) Exarkun expressed a strong (ok, mandatory) preference for the use of tw.cred in any Authentication solution. However, it was noted that tw.cred does not allow for challenge-response authentication mechanisms (which all of mine are). Specific examples were noted: twisted.protocols.sip, SASL, OTP
Hmm. How did you get this idea? Cred's design was specifically to facilitate challenge-response authentication. That's why login() takes credentials and returns a Deferred. The assumption is that the credentials object will encapsulate whatever facets of the user's connection are required to do the negotiation process.
While it might be a valid assumption, there is no common public interface provided to facilitate it. Perhaps because no one knows what that should look like, perhaps because no one felt it was necessary. But I do believe that something like twisted.protocols.sip.IAuthorizer, that allows for an arbitrary number of round trips should be in cred, either the ICredentials interface should be extended (probably through a subclass) or a new interface should be created. I'm toying with some ideas of how to best do this, but I don't really "get" cred so if you have any requirements other than arbitrary number of round trips, let me know so i can take those into account now rather than later.
I've been thinking more about this and I can indeed now see a slight impedance mismatch that I haven't yet thought my way around. I got here from thinking about implementing Negotiate support using PyGSS. Exarkun and I posted examples of how one might do arbitrary challenge-response by making the creds a proxy object to the protocol instance, and I waved my hands saying "HTTP is a bit more hard because of the statelessness / request-based nature of the protocol". The best I've come up with that in theory supports >1 auth mechanism[1] is here: http://deadbeefbabe.org/paste/2249 To "match" HTTP to t.cred, it seems the "credentials" *are* the HTTP request object (which in fact is true, given how the HTTP spec is worded I think?). There's then a checker that can "check" HTTP requests against sub-mechanisms, and if none work then one or all of them can challenge the request. This seems somewhat hackish as the "checker" then has to know about "name: value" rfc2822-style protocol PDUs, as opposed to just the elements of the auth algorithm. It might be more palatable if you could somehow "chain" checkers - e.g. return an IDigestCreds from requestAvatarId, or maybe have some kind of 2-pass thing? [FreeRadius has an interesting thing in it's "authorize" config section where you first pass the request through the "authorize" section to determine how to authenticate it (and optionally return there and then with challenges etc.) then pass it through just one module as defined by the auth-type. This will mean nothing to you unless you've seen it, but that's what I mean by 2-pass] On the other hand, this is a very flexible system as it permits you to allow based on other portions of the request such as arbitrary headers, SSL cert for the TCP connection, and so on. (Note to all: a load of the code is ripped from the t.p.sip code, so appropriate credit where it's due. The stateless nonce thing might be worth ripping though) Comments welcome. [1] t.w.http does not appear to allow you to set headers >1 time e.g. WWW-Authenticate: basic realm="server.domain.com" WWW-Authenticate: digest realm="...",foo="bar",...
On Sun, 2005-11-27 at 21:31 +0000, Phil Mayers wrote:
it seems the "credentials" *are* the HTTP request object (which in fact is true, given how the HTTP spec is worded I think?).
This is what I tried up doing; including the request in the Credentials. This works a bit, but it really isn't compatible with t.web.guard. Mostly because my Checker ends up doing things to the request, but Guard really had plans to do *other* things with that request once Portal.login returned, so it ends up in a bit of a wreck. Maybe it would work better if I used a livepage channel instead of a dumb request.
On Mon, 28 Nov 2005 18:18:28 -0800, Kevin Turner <kevin@janrain.com> wrote:
On Sun, 2005-11-27 at 21:31 +0000, Phil Mayers wrote:
it seems the "credentials" *are* the HTTP request object (which in fact is true, given how the HTTP spec is worded I think?).
This is what I tried up doing; including the request in the Credentials. This works a bit, but it really isn't compatible with t.web.guard. Mostly because my Checker ends up doing things to the request, but Guard really had plans to do *other* things with that request once Portal.login returned, so it ends up in a bit of a wreck. Maybe it would work better if I used a livepage channel instead of a dumb request.
It would be better if some specific interface were published via wrapping the request, so that the authentication code could be clearly recognizable. I don't think it makes sense to think of the request itself as the authentication interface or the credentials, especially as any interesting HTTP-based authentication scheme (even simple challenge/response digest auth) spans multiple requests.
glyph@divmod.com wrote:
On Mon, 28 Nov 2005 18:18:28 -0800, Kevin Turner <kevin@janrain.com> wrote:
On Sun, 2005-11-27 at 21:31 +0000, Phil Mayers wrote:
it seems the "credentials" *are* the HTTP request object (which in fact is true, given how the HTTP spec is worded I think?).
This is what I tried up doing; including the request in the Credentials. This works a bit, but it really isn't compatible with t.web.guard. Mostly because my Checker ends up doing things to the request, but Guard really had plans to do *other* things with that request once Portal.login returned, so it ends up in a bit of a wreck. Maybe it would work better if I used a livepage channel instead of a dumb request.
It would be better if some specific interface were published via wrapping the request, so that the authentication code could be clearly recognizable. I don't think it makes sense to think of the request
But the data you need to wrap is different for each mechanism, so all that does is move the knowledge of 1 protocol (HTTP) out of the checker (which I agree is hacky), and put the knowledge of N mechanisms into the protocol. It's possible this is unavoidable with HTTP :o(
itself as the authentication interface or the credentials, especially as any interesting HTTP-based authentication scheme (even simple challenge/response digest auth) spans multiple requests.
You're probably right. But there are non-obvious (to me at least) difficulties, in particular with keeping the HTTP protocol clear of knowledge about the specifics of the auth mechanisms, permitting >1 WWW-Authenticate challenge header, and keeping the HTTP server free of state to route challenge responses back to the appropriate deferreds (memory exhaustion attack waiting to happen). And also permitting the obligatory HTML-form-based fallback. I'm having difficulty seeing what it would look like, especially the bit of allowing a single portal and list of checkers to support the multi-mechanism bit. You need something pam-like or similar to traverse the checker list with "empty" creds, allow all checkers to challenge for their mechanism, then pass the challenge-response back to just the single mechanism that's chosen. At the moment, the best I can come up with is: class Request(http.Request): def process(self): variousCreds = [] if self.isSecure(): # Can't "challenge" at this stage, he doesn't need to know # about us variousCreds.append(SSLClientCert(self.sslFoo)) authz = self.getHeader('Authorization') if authz: mech, rest = authz.split(' ', 1) mech = mech.lower() mechCreds = httpMechWrapperFactory(mech, rest, self) variousCreds.append(mechCreds) # other stuff goes here e.g. URL arguments, pubcookie cookies, etc. self.portal.login(variousCreds) class MultiPortal(Portal): def login(self, credlist): for c in self.checkers: # All or a subset... if c.canHandle(credlist): return c.login(credlist) for c in self.checkers: c.maybeChallenge(credlist) ...which starts to look very different from cred as-is Perhaps this comes from a misunderstanding in the goals? Am I right to assume we want to support both the HTTP-standard mechs and also HTML-form-based ones? Am I right in assuming >1 mechanism challenge is a wanted? The counter question is, if not, can I assume whatever replaces guard will be swappable-out without breaking sessions and/or livepage?
On Tue, 29 Nov 2005 17:26:32 +0000, Phil Mayers <p.mayers@imperial.ac.uk> wrote:
[snip]
class MultiPortal(Portal): def login(self, credlist): for c in self.checkers: # All or a subset... if c.canHandle(credlist): return c.login(credlist) for c in self.checkers: c.maybeChallenge(credlist)
...which starts to look very different from cred as-is
I haven't had time to catch up on this thread, but I wanted to point out that it is pretty much always wrong to subclass Portal. If you ever find yourself doing this, you have found a real case cred does not support or (more likely, and I believe this is case here) you not succeeded in fitting whatever authentication scheme is at hand into cred. Changing the signature of portal.login() in this manner almost entirely defeats the purpose of cred, because it destroys the general nature of the API. I cannot use a protocol implemented against the above MultiPortal without having specific knowledge as to this implementation detail, nor can I re-use any existing portal which my application already goes to the trouble to create with it. This is a bad thing. Jean-Paul
Jean-Paul Calderone wrote:
On Tue, 29 Nov 2005 17:26:32 +0000, Phil Mayers <p.mayers@imperial.ac.uk> wrote:
[snip]
class MultiPortal(Portal): def login(self, credlist): for c in self.checkers: # All or a subset... if c.canHandle(credlist): return c.login(credlist) for c in self.checkers: c.maybeChallenge(credlist)
...which starts to look very different from cred as-is
I haven't had time to catch up on this thread, but I wanted to point out that it is pretty much always wrong to subclass Portal. If you ever
Yes, wanting to is a symptom of the underlying problem, which is that for HTTP and Digest auth, you have to send the initial challenge back with the 401. To do this, you have to communicate with the checker to generate said challenge, or implement the digest algo. inside the HTTP proto, which is way nasty and breaks sharing. This either means: * "just deciding" that the client *will* use digest, creating a digest creds object that will issue the 401 and challenge, and calling "login" with it - losing auth mechanism negotiation support, which is nasty * holding a reference to a "challenge" method on the digest checker, and potentially any other challenge-with-401 mechs, breaking the encapsulation, which is nasty * adding a pre-login-stage mechanism to portal, or otherwise futzing the API, which is nasty I would add that the listCredentialInterfaces method is not really sufficient for a completely configuration-free decision on what mechanisms to present the client - some (e.g. plain/login) may only be available over certain channels depending on policy, and deciding that policy may require a method call into the checker *instance* with a protocol-neutral description of the "connection characteristics" (e.g. the SSF in SASL) It is, in general, a non-trivial problem in any environment. None of these issues are specific to Twisted. Witness the hassle GSSAPI, SASL and EAP cause. But to drag this back on topic, the original discussion was that t.web2 was MANDATED to use t.cred for authentication, and it seems to me at least there are unanswered questions about how it will work. I'm sure it will, and am interested in working out how - hence the flurry of emails. Perhaps I'll have another go next week (since I am moving datacentre for the rest of this one)
participants (7)
-
Clark C. Evans
-
David Reid
-
glyph@divmod.com
-
Jean-Paul Calderone
-
Kevin Turner
-
Phil Mayers
-
Valentino Volonghi aka Dialtone