Пирамидальная авторизация для хранимых предметов - PullRequest
18 голосов
/ 23 апреля 2011

Я пытаюсь создать политику авторизации, которая учитывает владение "предметом".Например, некоторые пользователи X «владеют» элементами A, B, C. К ним обращаются через URL-адреса, такие как /item/{item}/some_options.

Как я могу получить информацию о {item} в объект политики авторизации (permits ()вызов)?Хорошая идея - добавить дополнительную информацию в контекст (я делаю только маршрутизацию на основе маршрутов).Как бы я это сделал?

1 Ответ

50 голосов
/ 23 апреля 2011

Это можно сделать с помощью ACLAuthorizationPolicy в сочетании с рассылкой URL-адресов, используя специальное дерево ресурсов, разработанное для этой цели.

Например, у вас есть разрешения для Foo объектов и разрешения для Bar объектов. Эти ACL могут быть найдены путем обхода дерева ресурсов с помощью URL:

/foos/{obj}
/bars/{obj}

Дерево ресурсов становится иерархией разрешений, где в любой точке дерева вы можете поместить __acl__ на объект ресурса:

root                       (Root)
|- foos                    (FooContainer)
|  `- {obj}                (Foo)
`- bars                    (BarContainer)
   `- {obj}                (Bar)

Вы можете представить эту иерархию в дереве ресурсов:

class Root(dict):
    # this is the root factory, you can set an __acl__ here for all resources
    __acl__ = [
        (Allow, 'admin', ALL_PERMISSIONS),
    ]
    def __init__(self, request):
        self.request = request
        self['foos'] = FooContainer(self, 'foos')
        self['bars'] = BarContainer(self, 'bars')

class FooContainer(object):
    # set ACL here for *all* objects of type Foo
    __acl__ = [
    ]

    def __init__(self, parent, name):
        self.__parent__ = parent
        self.__name__ = name

    def __getitem__(self, key):
        # get a database connection
        s = DBSession()
        obj = s.query(Foo).filter_by(id=key).scalar()
        if obj is None:
            raise KeyError
        obj.__parent__ = self
        obj.__name__ = key
        return obj

class Foo(object):
    # this __acl__ is computed dynamically based on the specific object
    @property
    def __acl__(self):
        acls = [(Allow, 'u:%d' % o.id, 'view') for o in self.owners]
        return acls

    owners = relation('FooOwner')

class Bar(object):
    # allow any authenticated user to view Bar objects
    __acl__ = [
        (Allow, Authenticated, 'view')
    ]

С помощью такой настройки вы можете сопоставить шаблоны маршрутов с вашим деревом ресурсов:

config = Configurator()
config.add_route('item_options', '/item/{item}/some_options',
                 # tell pyramid where in the resource tree to go for this url
                 traverse='/foos/{item}')

Вам также необходимо сопоставить свой маршрут с определенным видом:

config.add_view(route_name='item_options', view='.views.options_view',
                permission='view', renderer='item_options.mako')

Отлично, теперь мы можем определить наше представление и использовать загруженный контекстный объект, зная, что если представление выполняется, у пользователя есть соответствующие разрешения!

def options_view(request):
    foo = request.context
    return {
        'foo': foo,
    }

Используя эту настройку, вы используете значение по умолчанию ACLAuthorizationPolicy и предоставляете разрешения на уровне строк для своих объектов с помощью функции «Диспетчеризация URL». Также обратите внимание, что поскольку объекты устанавливают свойство __parent__ для дочерних элементов, политика будет накапливать родословную, наследуя разрешения от родителей. Этого можно избежать, просто вставив * ACE DENY_ALL в свой ACL или написав собственную политику, не использующую происхождение контекста.

* Обновление * Я превратил этот пост в настоящую демонстрацию на Github. Надеюсь, это кому-нибудь поможет. https://github.com/mmerickel/pyramid_auth_demo

* Обновление * Я написал полное руководство по системе аутентификации и авторизации пирамиды здесь: http://michael.merickel.org/projects/pyramid_auth_demo/

...