[Twisted-Python] New Guard question

Glyph Lefkowitz glyph at twistedmatrix.com
Sat Jun 21 02:05:47 EDT 2003


On Thursday, June 19, 2003, at 02:33 PM, jml at ids.org.au wrote:

> I've been trying to figure out the new guard and cred, with the goal 
> of perhaps
> switching Issues over to use guard rather than the custom auth it does 
> now.

Hooray!  (Subtext - everyone should do this and give feedback)

> Looking at the webhappyrealm.py example in the sandbox, it's not very 
> clear
> what the returned resources represent.

Ah yes.  This is due to my peculiar ... idiom, when dealing with web 
login.

> Also, returning Resources seems a little weird. To me, a resource is 
> whole
> page, why would I want a whole page returned?

You're going to have to remember that I still think of Twisted as a big 
multiplayer game, and all this HTTP stuff is just kind of a grotty way 
to display room descriptions.  If you haven't already done so, please 
check your assumptions about web development at the door.  It's cool, I 
can wait.

Okay then.

So, the idea here is that with any login to an interactive system, you 
have a session.  You log in, you interact with the system for a while, 
and then you log out.  Conceptually, there is an object on a "client" 
that connects to an object on a "server".  [New] Cred's model for 
representing this interaction is the connection between the 
AvatarAspect and the Mind (see previous email for definitions of the 
contentious verbiage), which is established when 
IRealm.requestAvatar(...) is called, and ends when the 'logout' method 
returned from that call is called.

The AvatarAspect that is returned is expected to conform to an 
interface (in this case, IResource) that has some meaning to the 
protocol it's going to be talking to.  In the simplest case, for 
example, some cred junk that merely password protects a directory 
structure, this resource can be the same for all users.

However, when the application that you're interacting with is actually 
an *application*, and you're trying to mimic a "real" client program 
with the browser, you want to provide custom logic for each user who 
logs in.

Now to answer the question you actually asked: the Resource that 
woven.guard is requesting from your Realm is supposed to represent the 
UI to present to a particular user of your application.  In theory, 
you've got some domain data (perhaps an IssuesPerson) which represents 
the user (their Persona) - you can wrap a Resource around this and then 
provide web-specific functionality to communicate with your domain 
objects; hooray, it is object-oriented programming for the web.

Then, this Resource is not only a "whole page", but an encapsulation of 
the user's entire experience of the web component of your system.  
Typically you will provide a customized top-level facade that has a 
small amount of dynamic content and then references to shared objects, 
such as static data on the filesystem or dynamic objects which are 
calculated on each view but are the same for all users.

That was how to do things right.  Now I will explain why you should not 
do them wrong.

You may be wondering, "but what about the session!? I can keep this 
data on a session object, can't I?"  The answer is "yes, but why would 
you?"  Even given the fact that Session is componentized and may 
provide custom, separated functionality, this is a protocol detail that 
leads to unpleasant code.  Consider, if you want to use woven.guard's 
nifty session-negotiation feature, but also keep your user-specific 
code in a session, your code will look like this:

     # WHAT FOLLOWS IS AN EXAMPLE OF A MISTAKEN ASSUMPTION THAT IS 
COMMON DUE TO
     # HOW AWFUL THE WEB IS.  PLEASE DO NOT WRITE NEW-CRED WEB 
APPLICATIONS THIS
     # WAY.

     from twisted.web.resource import Resource
     from twisted.cred.badidea import ICredSession

     class MySingletonResource(Resource):
         data = 'hello %s, do you like sunshine?'
         def __init__(self, realm):
             Resource.__init__(self)
             self.realm = realm

         def render(self, request):
             # negotiate session
             s = request.getSession()
             if s is None:
                 return request.setupSession()

             # return data
             request.setHeader('content-type', 'text/plain')
             c = s.getComponent(ICredSession)
             if c is None:
                 return 'it looks like you are anonymous please log in!'
             else:
                 avatar = c.getLoggedInForRealm(self.realm)
                 return self.data % avatar.username

Now, isn't that ugly?  Granted, the guard-style session negotiation 
makes it slightly more perverse, but regardless, there will be 
boilerplate in every render() and getChild() for every resource you 
have in order to identify the appropriate user, plus code for 
Anonymous, unless you make your resources depend upon a specifc 
execution context to work.

If this seems normal to you, or not particularly unpleasant, have a 
look at some sample code for the suggested approach:

     # THIS, ON THE OTHER HAND, IS NOT SO BAD.
     from twisted.web.resource import Resource

     class MyResource(Resource):
         data = 'hello %s, do you like sunshine?'

         def __init__(self, avatar):
             Resource.__init__(self)
             self.avatar = avatar

         def render(self, request):
             request.setHeader('content-type', 'text/plain')
             return self.data % self.avatar.username

     class MyAnonymousResource(Resource):

         def render(self, request):
             return 'it looks like you are anonymous please log in!'

I mentioned games at the start of the email.  This sort of approach is 
important in a game system because you want to perform on-the-fly 
updates of the user's interface without necessarily altering the state 
of their avatar object.  Giving every user's connection its own 
resource is much more flexible.  Even in this small example, we've 
exposed some small customizability by accident: the realm could 
maintain an active pool of MyResource instances and set self.data = 
'Sorry %s, *** YOU HAVE DIED ***' on one of them; the next time the 
user loads their page, they will see a different message.  There is 
nowhere to put this in MySingletonResource.

(Of course, the right way to do this is with a Mind object for HTTP, 
but the browser will be unable to do any real updating unless it is 
taking Donovan's psychoactive LivePage javascript.  That particular 
potent mix is unfortunately not yet suitable for beginners, but newcred 
should be soon.  Think of woven.guard as a 'gateway drug' to LivePage's 
full flexibility.)

Please feel free to ask more questions, as I'd like to provide a 
resource for doc-writers here on the list :)





More information about the Twisted-Python mailing list