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?