Slide 1

Slide 1 text

Authentication and Authorization Policy Attempt to bring some lights in this catacomb Wednesday, 1 January 14

Slide 2

Slide 2 text

Hello, Bonjour Rach Belaid Python Developer, Postgres Fan, Git Lover, Belgian Erlang Hobbyist, rach @rachbelaid rachbelaid.com [email protected] Wednesday, 1 January 14

Slide 3

Slide 3 text

Authentication ≠ Authorization Authentication verifies who you are Authorization verifies what you are authorized to do Wednesday, 1 January 14

Slide 4

Slide 4 text

Why? User Users Resources Repo Issue Wiki Org Billing Anonymous User User ACL ACL ACL A Wednesday, 1 January 14

Slide 5

Slide 5 text

Authentication Policies Simply to handle the Login / Logout and you use it by calling : from pyramid.security import * remember(request, userid) -> headers forget(request) -> headers .... Wednesday, 1 January 14

Slide 6

Slide 6 text

Authentication Policies Pyramid offer many policies: - AuthTktAuthenticationPolicy - SessionAuthenticationPolicy - BasicAuthAuthenticationPolicy ..... Wednesday, 1 January 14

Slide 7

Slide 7 text

Authorization policy Pyramid provide: - ACLAuthorizationPolicy This policy consults an ACL attached to a context/resource and determine authorization information. Wednesday, 1 January 14

Slide 8

Slide 8 text

Example - Simple App from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_view(hello_world, route_name='home') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() Wednesday, 1 January 14

Slide 9

Slide 9 text

Auth’s setup from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() authn_policy = AuthTktAuthenticationPolicy('seekrit') authz_policy = ACLAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_route('home', '/') config.add_view(hello_world, route_name='home') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() Wednesday, 1 January 14

Slide 10

Slide 10 text

Protecting Views with Permissions config.add_view(perm_view, route_name='resource', permission='view') @view_config(route_name='home', permission='view') Wednesday, 1 January 14

Slide 11

Slide 11 text

Login and Logout Views @view_config(route_name='login') def login(request): username = request.matchdict['username'] #check valids headers = remember(request, username) return HTTPFound(location='/' , headers=headers) @view_config(route_name='logout') def logout(request): headers = forget(request) return HTTPFound(location='/' , headers=headers) Wednesday, 1 January 14

Slide 12

Slide 12 text

Terminology - ACL - ACE - Resource and Resource Tree - Lineage - Principal - Root Factory Wednesday, 1 January 14

Slide 13

Slide 13 text

ACL and ACE - ACL: Access control list. An ACL is a sequence of ACE tuples - ACE : Access control entry a three-tuple that describes three things: an action (Allow or Deny), a principal (a string describing a user or group), and a permission __acl__ = [ (Allow, 'fred', 'view'), (Allow, 'henry', 'view') ] (Allow, 'fred', 'view') Wednesday, 1 January 14

Slide 14

Slide 14 text

principal A principal is a string object representing a user id or a group id Wednesday, 1 January 14

Slide 15

Slide 15 text

Resource & Resources Tree Representing object to access in the resource tree of an application. Tree : A nested set of dictionary-like objects, each of which is a resource Wednesday, 1 January 14

Slide 16

Slide 16 text

Lineage The lineage of a resource is composed of itself, its parent, its parent’s parent, and so on. If part of a lineage, the context’s parents are consulted for the ACL information too. Wednesday, 1 January 14

Slide 17

Slide 17 text

Root Factory This object will usually be used as the context resource config.add_route('idea', 'ideas/{idea}', factory=Idea) config.add_view('myproject.views.idea_view', route_name='idea') ... class Idea(object): def __init__(self, request): matchdict = request.matchdict idea = matchdict.get('idea', None) if idea == 'mine': self.__acl__ = [ (Allow, 'editor', 'view') Wednesday, 1 January 14

Slide 18

Slide 18 text

Type & instance permission __acl__ attribute can be defined on the resource instance if you need instance- level security, or it can be defined on the resource class if you just need type- level security. Wednesday, 1 January 14

Slide 19

Slide 19 text

Assigning ACLs to your Resource Objects class Repo(Base): __acl__ = [ (Allow, 'jack', 'view')] class Billing(object): def __init__(self, request): matchdict = request.matchdict self.id = matchdict.get('id', None) if self.id == '1': self.__acl__ = [ (Allow, 'henry', 'view'), (Allow, 'jack', 'view') ] On the class On the instance Wednesday, 1 January 14

Slide 20

Slide 20 text

ACL Inheritance if a resource object does not have an ACL when it is the context, its parent is consulted for an ACL. class Repo(Base): __acl__ = [ (Allow, 'jack', 'view') ] class Wiki(Base): __name__ = 'wiki' __parent__ = Repo Wednesday, 1 January 14

Slide 21

Slide 21 text

Example: Imports from pyramid.config import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from pyramid.view import view_config from pyramid.security import Allow, remember, forget from pyramid.security import authenticated_userid from pyramid.httpexceptions import HTTPFound Wednesday, 1 January 14

Slide 22

Slide 22 text

Example: Resources class Base(object): def __init__(self, request): matchdict = request.matchdict self.id = matchdict.get('id', None) class Repo(Base): __acl__ = [ (Allow, 'jack', 'view')] class Wiki(Base): __name__ = 'wiki' __parent__ = Repo class Issue(Base): __name__ = 'issue' __parent__ = Repo class Org(Base): __acl__ = [ (Allow, 'fred', 'view'), (Allow, 'henry', 'view') ] class Billing(Base): __name__ = 'billing' def __init__(self, request): super(Billing,self).__init__(request) if self.id == '1': self.__acl__ = [ (Allow, 'henry', 'view'), (Allow, 'jack', 'view') ] self.__parent__ = Org Wednesday, 1 January 14

Slide 23

Slide 23 text

Example: App def main(global_config, **settings): settings = dict(settings) authn_policy = AuthTktAuthenticationPolicy('secret') authz_policy = ACLAuthorizationPolicy() config = Configurator( settings=settings) config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) #route config.add_route('login', '/login/{username:\w+}') config.add_route('logout', '/logout') config.add_route('hello', '/') config.add_route('repo', '/repo/{id:\d+}', factory=Repo) config.add_route('issue', '/issue/{id:\d+}', factory=Issue) config.add_route('wiki', '/wiki/{id:\d+}', factory=Wiki) config.add_route('org', '/org/{id:\d+}', factory=Org) config.add_route('billing', '/billing/{id:\d+}',factory=Billing) #add views config.scan() return config.make_wsgi_app() Wednesday, 1 January 14

Slide 24

Slide 24 text

Example: View @view_config(route_name='login') def login(request): headers = remember(request, request.matchdict['username']) return HTTPFound(location='/' , headers=headers) @view_config(route_name='logout') def logout(request): headers = forget(request) return HTTPFound(location='/' , headers=headers) @view_config(route_name='hello', renderer='string') def hello(request): userid = authenticated_userid(request) return 'Hello %s' % userid @view_config(route_name='repo', renderer='string', permission='view') @view_config(route_name='wiki', renderer='string', permission='view') @view_config(route_name='issue', renderer='string', permission='view') @view_config(route_name='org', renderer='string', permission='view') @view_config(route_name='billing', renderer='string', permission='view') def perm(request): return 'ok' Wednesday, 1 January 14

Slide 25

Slide 25 text

Example Results - henry, fred has access to all billings - jack has access to billing 1 - jack has access to Repo, Issue, Wiki Wednesday, 1 January 14

Slide 26

Slide 26 text

Let’s clean that ... with a bit of traversal ... no request handling in your resources Url dispatch + Traversal = AWESOME Wednesday, 1 January 14

Slide 27

Slide 27 text

Example: Imports from pyramid.config import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from pyramid.view import view_config from pyramid.security import Allow, remember, forget from pyramid.security import authenticated_userid from pyramid.httpexceptions import HTTPFound Wednesday, 1 January 14

Slide 28

Slide 28 text

Example: Resources class Repo(object): __acl__ = [ (Allow, 'jack', 'view')] class Wiki(object): __name__ = 'wiki' __parent__ = Repo class Issue(object): __name__ = 'issue' __parent__ = Repo class Org(object): __acl__ = [ (Allow, 'fred', 'view'), (Allow, 'henry', 'view') ] class Billing(object): __name__ = 'billing' def __getitem__(self, key): if key == '1': self.__acl__ = [ (Allow, 'henry', 'view'), (Allow, 'jack', 'view') ] self.__parent__ = Org Wednesday, 1 January 14

Slide 29

Slide 29 text

Example: App def main(global_config, **settings): settings = dict(settings) .... same as before .... #route config.add_route('login', '/login/{username:\w+}') config.add_route('logout', '/logout') config.add_route('hello', '/') config.add_route('repo', '/repo/{id:\d+}', factory=lambda x: Repo(), traverse= '/{id:\d+}') config.add_route('issue', '/issue/{id:\d+}', factory=lambda x: Issue(), traverse= '/{id:\d+}') config.add_route('wiki', '/wiki/{id:\d+}', factory=lambda x: Wiki(), traverse= '/{id:\d+}') config.add_route('org', '/org/{id:\d+}', factory=lambda x: Org(), traverse= '/{id:\d+}') config.add_route('billing', '/billing/{id:\d+}',factory=lambda x: Billing() traverse= '/{id:\d+}') #add views config.scan() return config.make_wsgi_app() Wednesday, 1 January 14

Slide 30

Slide 30 text

Example: View @view_config(route_name='login') def login(request): headers = remember(request, request.matchdict['username']) return HTTPFound(location='/' , headers=headers) @view_config(route_name='logout') def logout(request): headers = forget(request) return HTTPFound(location='/' , headers=headers) @view_config(route_name='hello', renderer='string') def hello(request): userid = authenticated_userid(request) return 'Hello %s' % userid @view_config(route_name='repo', renderer='string', permission='view') @view_config(route_name='wiki', renderer='string', permission='view') @view_config(route_name='issue', renderer='string', permission='view') @view_config(route_name='org', renderer='string', permission='view') @view_config(route_name='billing', renderer='string', permission='view') def perm(request): return 'ok' Wednesday, 1 January 14

Slide 31

Slide 31 text

Example Results - henry, fred has access to all billings - jack has access to billing 1 - jack has access to Repo, Issue, Wiki Wednesday, 1 January 14

Slide 32

Slide 32 text

Callback def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) def main(global_config, **settings): settings = dict(settings) authn_policy = AuthTktAuthenticationPolicy('secret',callback) authz_policy = ACLAuthorizationPolicy() config = Configurator( settings=settings) config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) #route ..... #add views config.scan() return config.make_wsgi_app() expected to return None if the userid doesn’t exist Wednesday, 1 January 14

Slide 33

Slide 33 text

Helpers from pyramid.security import unauthenticated_userid() -> user_id authenticated_userid() -> user_id but with authorization callback Wednesday, 1 January 14

Slide 34

Slide 34 text

So much more! Special permission Permission with Traversal Special Action Default permission Wednesday, 1 January 14

Slide 35

Slide 35 text

References - Pyramid docs - Let’s ride bikes - Pycon 2013 from Chris McDonough https://github.com/mcdonc/bikes Wednesday, 1 January 14