On Wed, Nov 10, 2004 at 10:18:07AM -0800, Evan LaForge wrote:
>
> > My application has to authenticate a user, determine which group
> > (singular) they belong to, then enforce group permissions. (I'm
> > vaguely thinking of Zope's roles.) The permissions range from including
> > certain fields in the display to accessing update screens. However, the
> > anonymous user can also do searches and get limited displays without
> > logging in. Management wants people to be able to log in at any point
> > and continue their search-in-progress with upgraded permissions. This
> > implies a login link on every page, which goes to a login form, then
> > back to the previous page, now displaying more fields or records.
>
> This is sort of what my webmail app does. _q_access does 3 things: if your
> query string has magic __passwd and __login fields, it makes a new session for
> you and lets you go; if you're logged in, it lets you go; otherwise, it throws
> Not_logged_in. _q_exception_handler snags Not_logged_in and returns the login
> page. The login page packs up the query string in hidden fields and adds
> __passwd and __login fields, setting the action target to wherever you really
> wanted to go before the login machinery snagged you.
Thanks for everybody's suggestions. I went back to using _q_exception_handler
to display the login page but I'm still having _q_access build the page, so
that all the login logic is in one plage. So I was able to eliminate my
Publisher class. But I ended up making another Publisher class anyway,
because Publisher.filter_output proved to be a convenient place to add the
HTML header and footer to my pages, and also to add some HTTPRequest
goodies.
I would suggest making HTTPRequest subclassable in the core so that
people can add their own features:
class Publisher:
HTTPRequestClass = HTTPRequest
HTTPUploadRequestClass = HTTPUploadRequest
def create_request(self, stdin, env):
"""Use softcoded *Request classes instead of hardcoded."""
ctype = get_content_type(env)
if ctype == "multipart/form-data":
req = self.HTTPUploadRequestClass(stdin, env,
content_type=ctype)
req.set_upload_dir(self.config.upload_dir,
self.config.upload_dir_mode)
return req
else:
return self.HTTPRequestClass(stdin, env, content_type=ctype)
My filter_output (in the Publisher subclass) looks like this:
def filter_output(self, req, output):
"""Add our site HTML header and footer around the output."""
content_type = req.response.get_header('content-type')
if content_type is None or content_type.startswith('text/html'):
top = self.htmlPrepend(req)
bottom = self.htmlAppend(req)
output = str(top) + str(output) + str(bottom)
return _Publisher.filter_output(self, req, output)
htmlPrepend needs some data from the "servlet" (title, breadcrumbs,
extra message), which are passed through the request:
class RequestMixin:
def initRequestMixin(self):
self.title = None
self.htTitle = None
self.crumbs = None
self.extra = None
self.longPage = False
class HTTPRequest(RequestMixin, _HTTPRequest):
def __init__(self, *args, **kw):
_HTTPRequest.__init__(self, *args, **kw)
self.initRequestMixin()
class HTTPUploadRequest(RequestMixin, _HTTPUploadRequest):
def __init__(self, *args, **kw):
_HTTPUploadRequest.__init__(self, *args, **kw)
self.initRequestMixin()
I'd like to use super() but Request is a classic class. :( I remember
seeing a super() function somewhere that worked with either type of
class, so I'm on the hunt for it.
> BTW, my webmail app also uses multiple threads, if you want to see how it
> could be done. But unless you want a lot of shared state between processes
> (like a shared cache), then you're probably better off with the multiple
> process SCGI model. FastCGI also does multiple processes automatically, but
> it's more complicated than SCGI.
That's good to know, thanks. No, I don't anticipate significant shared
state. Just long queries. I'm beginning to think 'shelve' is unsafe
even with the CGI adapter, since it does say not to let two processes
open a shelve. So I'll prob'ly end up saving the sessions in MySQL
anyway.
> Maybe a Group object whose instances are like tokens? More powerful groups
> are subclasses, so each page (or each pages _q_access) can have a
> require_group(request.session, Some_group) that does
> any(lambda g: isinstance(g, Some_group), session.user.groups)
Most of the permission issues have to do with showing fields within the
page, not access to the page. I'll move up to a separate Group object
if I find a need for it, but so far it hasn't been necessary.
--
-Mike Orr (aka. Sluggo), mso@oz.net (iron@sense-sea-MegaSub-1-465.oz.net)
http://sluggo.kicks-ass.org/ Cxu vi parolas Esperante?