Fwd: [Twisted-Python] New Guard question
Glyph Lefkowitz
glyph at twistedmatrix.com
Sat Jun 21 17:10:35 MDT 2003
Begin forwarded message:
> From: adalke at mindspring.com
> Hi Glyph,
>
> I'm travelling right now, and can't send from the account I use
> to send mail to Twisted, so I'm sending this via personal email.
> Feel free to CC to Twisted as needed.
Your wish is my command.
> You gave two examples, a badidea one and a sketch of a new
> one. I think you are presenting a straw-man badidea.
They were demonstrations of a coding style which I have actually seen
several examples of. I think you're actually agreeing with me, because
I can't find anything in this message that indicates otherwise.
Let me be clear on the distinction between these styles: what I am
saying is *good* is keeping the avatar in the resource (which the
new-cred enabled UsernamePasswordWrapper pretty much requires).
This is good because resources are lightweight and it establishes a
good encapsulation barrier. Rather than having a Resource which
depends on state in the session, or a non-Resource which does
resource-like things and accept different arguments. You have a
resource that can _always_ render itself as long as it is internally
consistent.
What I am saying is *bad* is retrieving the avatar from the session
every time in your own application code. Now, obviously the code-path
to retrieve the avatar object does have to interact with your session,
because HTTP sucks, but this is tricky and you should wrap it if you
can. Guard does it for you, and I think it does a good job, but if it
doesn't you should probably contribute fixes or suggestions for how it
could be better, and not write your own session-masher.
> Were I to write such code, it would look like
>
> import weakref
> request_info = WeakRef.WeakKeyDictionary()
>
> class MyGuardResource(Resource):
> def __init__(self, guarded_resource, realm):
> Resource.__init__(self)
> self.realm = realm
> self.guarded_resource = guarded_resource
>
> def getChild(self, name):
> # Fix as needed if getChild is supposed to return None for no
> named child
> return MyGuardResource(self.realm,
> self.guarded_resource.getChild(name))
>
> def render(self, request):
> # negotiate session
> s = request.getSession()
> if s is None:
> return request.setupSession()
>
> # Logged in?
> c = s.getComponent(ICredSession)
> if c is None:
> request.setHeader('content-type', 'text/plain')
> return 'it looks like you are anonymous please log in!'
> avatar = c.getLoggedInForRealm(self.realm)
> # Either use a weakref like this
> request_info[request] = {"avatar": avatar}
> return self.guarded_resource.render(request)
> # or change the call API for request, like
> # return self.guarded_resource.render(request, avatar)
In this example, self.guarded_resource would not be a Resource, but
some special GuardedResource subclass which produced output for an
avatar rather than a request.
> Neither the weakref nor the tweaked parameters are
> particularly satisfying. I could make it nicer by assuming I
> can call the Resource constructor, as you seem to allow.
But then, what would you call the Resource constructor with? Perhaps
the avatar? :).
> It's hard to compare to your "not so bad" example because
> you don't show how your two resources are added to the
> system. As written, you don't allow either one to have dynamic
> children, so that being the case, the following would be a
> simpler replacement for your "badidea" code
The "bad idea" resource is a resource that is statically added on the
"web" side of things by a putChild or a .rpy or somesuch, and thus can
only know what realm it is being added for, and must be added in
parallel with the guard object.
The "not so bad" resource is meant to be instantiated by a Realm. Most
likely, adaption to the appropriate interface using the components
system will be automated by some common Realm base class when we add
one, but for now it's up to you to figure out to return a Resource. In
this case, only the UsernamePasswordWrapper has to know about the
Portal, and the realm can create an appropriate Resource without
knowing anything else about the structure of other web code.
Actually the "bad idea" resource can't do dynamic children without
doing boilerplate, whereas the "not so bad" resource may implement
getChild in a naive way and it will work properly.
> class MySimpleGuardResource(Resource):
> def __init__(self, realm, authorized, not_authorized):
> Resource.__init__(self):
> self.realm = realm
> .. same for the other two parameter...
> def render(self, request):
> # negotiate session
> ... getSession() .. if is None ... setupSession
> c = s.getComponent(ICredSession)
> if c is None:
> return self.not_authorized().render(session)
> return self.authorized(c).render(session)
>
> in which case I can identically use your not-so-bad
> classes.
Well, yes, that is the whole point of guard.
> So you say:
>> 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:
Let me clarify:
If you want to use woven.guard's nifty session-negotiation feature, but
also implement service-specific functionality in the resource and
retrieve it from the session yourself rather than just creating an
intelligent stateful resource, your code will look like this:
> but I don't see why the latter follows from the former, given that with
> a simple adapter it appears to allow your code.
... which is what Guard is doing. The point is that I am encouraging
users to use this adapter and have the Realm create an appropriate
resource by calling its constructor, rather than attempting to have a
resource initialized dumb and then re-customize itself on each call to
render().
If you still really think this is a straw-man example of bad style,
then I applaud your optimism, but it's seriously a very common mistake
:-). After all, most web frameworks have the notion of rendering a
page as pretty basic, and then things that are "dynamic" are
implemented with do-I-have-a-logged-in-user conditionals, not object
references.
> Mind you, I'm still trying to understand how to do authentication in
> Twisted, either cred or mind style. I know how to do it myself, so
> that's my starting point.
"cred or mind style"? There are only two styles, one of which is
old-cred (Service/Perspective/Authorizer), which is awful and horrible
and don't use it please unless you have to interact with code that
hasn't been ported from it yet, and new-cred
(Realm/Avatar/CredentialsChecker), which will be available in 1.0.6,
and all this discussion is about.
> Regarding names, IMHO, "Guard" is a better name than "Portal",
> and you could use "UsernamePasswordGuard", etc. rather than
> "...Wrapper".
Hmm. 'Guard' is a different thing from 'Portal', but I think that
you're right about UsernamePasswordGuard rather than
UsernamePasswordWrapper.
More information about the Twisted-Python
mailing list