I’ve found a decent solution (and made it work, which took another couple days). This probably would have gone a lot faster and easier if I was already used to working with Python and/or libraries and frameworks, but it was a great learning experience.

To keep this short, here is how you can add individual permissions to TurboGears with repoze.what quickstart:

You will need to modify or monkeypatch repoze/what/middleware.py lines 99-100 and 105-106.

Before:

        identity['groups'] = groups
        identity['permissions'] = permissions

        environ['repoze.what.credentials']['groups'] = groups
        environ['repoze.what.credentials']['permissions'] = permissions

After:

        identity.setdefault('groups', set()).update(groups)
        identity.setdefault('permissions', set()).update(permissions)

        environ['repoze.what.credentials']['groups'] = identity['groups']
        environ['repoze.what.credentials']['permissions'] = identity['permissions']

Hopefully repoze.what will be updated soon to reflect this change. repoze.who (completely separate from repoze.what) actually supports as many authorization providers as you want to give it, but it stores them in a dictionary. This means that you can’t rely on the order they are called in, so directly assigning a value to the groups and permissions dictionaries will overwrite anything that is already there.

After that, it’s actually pretty easy, so long as you aren’t trying to follow all the instances and variables through the framework. In your app/config/middleware.py you will need to add your own IMetadataProvider to base_app.sa_auth['mdproviders']. repoze.what does play nicely here – it makes sure to union its own adapters rather than directly assign them.

Your class needs to implement IMetadataProvider, but all you actually have to do is create an __init__ method to store an instance in and an add_metadata method to add the new permissions to the environment when repoze.what is doing that step. Here’s mine, which is probably awful since it may well be the first actual Python class I’ve ever written:

class UserPermissionsMetadata:
    implements(IMetadataProvider)
    def __init__(self, permissions_adapter):
        self._getperms = permissions_adapter

    def add_metadata(self, environ, identity):
        logger = environ.get('repoze.who.logger')
        userid = identity['repoze.who.userid']
        credentials = identity.copy()
        credentials['repoze.what.userid'] = userid

        # Returns a set of permissions
        permissions = self._getperms.find_sections(credentials)

        identity.setdefault('permissions', set()).update(permissions)

        # forward compatibility
        if 'repoze.what.credentials' not in environ:
                environ['repoze.what.credentials'] = {}
        environ['repoze.what.credentials']['permissions'] = identity['permissions']

        logger and logger.info('User has the following explicit permissions: %s' % str(permissions))

If you’re observant or familiar with the source, you’ll recognize this as simply a stripped down version of AuthorizationMetadata from repoze.what.middleware.

Before this will work, we also need an instance of the SQL adapter that actually accesses and returns the data. We can actually just steal SqlGroupsAdapter to accomplish this, since it behaves the same way we need to; it’s probably cleaner to write a new class entirely though.

from repoze.what.plugins.sql.adapters import SqlGroupsAdapter
permissions_adapter = SqlGroupsAdapter(base_config.sa_auth.permission_class,
                                       base_config.sa_auth.user_class,
                                       base_config.sa_auth.dbsession)
permissions_adapter.translations = {
            'section_name': 'permission_name',
            'sections': 'permissions',
}

This part comes from repoze.what.plugins.sql.adapters. I modify the translations attribute to pull data from a different section (the actual code uses group_name and groups). I hit a snag here; I had to supply the translated names of my tables rather than the pre-translated versions. I haven’t quite worked out why, but right now I’m just happy it runs as intended.

Finally, we add our adapter to mdproviders, which gets compiled into the list of providers that repoze.who calls for various things.

auth = UserPermissionsMetadata(permissions_adapter)
if 'mdproviders' not in base_config.sa_auth:
    base_config.sa_auth['mdproviders'] = []

base_config.sa_auth['mdproviders'].append(('authorization_md', auth))

And lastly, the call that kicks off all the quickstart stuff:

make_base_app = base_config.setup_tg_wsgi_app(load_environment)

You must add to the mdproviders dictionary before the setup_tg_wsgi_app call, since down that list of calls comes the call to make_middleware that actually processes all the middleware and stores it away in the various attributes. We could add our metadata provider on later I’m sure, but this seemed the most direct route.

That code will do nothing, of course, if you don’t set up a junction table between your users and permissions tables in app/model/auth.py – once again, I just based it on the users/groups junction table. I added a new relationship in the Users definition, and renamed the ‘permissions’ property so as not to conflict with the new ‘permissions’ relationship. There may be a nicer way to do that, too.

Anyway, this should get you going! You can add permissions to users just like you add them to groups for more granular control over things without a bunch of redundant groups in your database. And you don’t even need 500 lines of notes!

 

 

Post filed under Technology.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>