durusmail: quixote-users: Authentication/permissions
Authentication/permissions
2004-11-08
2004-11-09
2004-11-10
2004-11-10
Authentication/permissions
Mike Orr
2004-11-10
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?

reply