Our first step introduced security ACL statements on our site root. In this next example, we add the ability to log in and log out.
$ cd ../../security; mkdir step02; cd step02
Copy the following into step02/application.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.authentication import AuthTktAuthenticationPolicy
from resources import bootstrap
from usersdb import groupfinder
def main():
config = Configurator(
root_factory=bootstrap,
authentication_policy=AuthTktAuthenticationPolicy(
'seekr1t',
callback=groupfinder)
)
config.scan("views")
app = config.make_wsgi_app()
return app
if __name__ == '__main__':
app = main()
server = make_server(host='0.0.0.0', port=8080, app=app)
server.serve_forever()
|
Copy the following into step02/views.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from pyramid.httpexceptions import HTTPForbidden
from pyramid.security import remember
from pyramid.security import forget
# Get our database that manages users
from usersdb import USERS
from pyramid.security import has_permission
class ProjectorViews(object):
def __init__(self, context, request):
self.context = context
self.request = request
@view_config(renderer="templates/default_view.pt",
permission='view')
def default_view(self):
can_i_edit = has_permission("edit", self.context,
self.request)
return dict(page_title="Site View",
can_i_edit=can_i_edit)
@view_config(renderer="templates/default_view.pt",
permission='edit',
name="edit")
def edit_view(self):
return dict(page_title="Edit Site")
@view_config(renderer="templates/login.pt", context=HTTPForbidden)
@view_config(renderer="templates/login.pt", name="login.html")
def login(self):
request = self.request
login_url = request.resource_url(request.context, 'login.html')
referrer = request.url
if referrer == login_url:
referrer = '/' # never use the login form itself as came_from
came_from = request.params.get('came_from', referrer)
message = ''
login = ''
password = ''
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
if USERS.get(login) == password:
headers = remember(request, login)
return HTTPFound(location=came_from,
headers=headers)
message = 'Failed login'
return dict(
page_title="Login",
message=message,
url=request.application_url + '/login.html',
came_from=came_from,
login=login,
password=password,
)
@view_config(name="logout.html")
def logout(self):
headers = forget(self.request)
url = self.request.resource_url(self.context, 'login.html')
return HTTPFound(location=url, headers=headers)
|
Copy the following into step02/resources.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | from pyramid.security import Allow
from pyramid.security import Everyone
class Folder(dict):
def __init__(self, name, parent, title):
self.__name__ = name
self.__parent__ = parent
self.title = title
class SiteFolder(Folder):
__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'group:editors', 'edit')
]
class Document(object):
def __init__(self, name, parent, title):
self.__name__ = name
self.__parent__ = parent
self.title = title
root = SiteFolder('', None, 'Projector Site')
from pyramid.security import DENY_ALL
def bootstrap(request):
# Let's make:
# /
# doc1
# doc2
# folder1/
# doc1
doc1 = Document('doc1', root, 'Document 01')
root['doc1'] = doc1
doc2 = Document('doc2', root, 'Document 02')
doc2.__acl__ = [
(Allow, Everyone, 'view'),
(Allow, 'group:admin', 'edit'),
DENY_ALL
]
root['doc2'] = doc2
folder1 = Folder('folder1', root, 'Folder 01')
root['folder1'] = folder1
return root
|
Copy the following into step02/usersdb.py:
1 2 3 4 5 6 7 8 9 10 11 | USERS = {'editor': 'editor',
'viewer': 'viewer'}
GROUPS = {'editor': ['group:editors']}
def groupfinder(userid, request):
# Has 3 potential returns:
# - None, meaning userid doesn't exist in our database
# - An empty list, meaning existing user but no groups
# - Or a list of groups for that userid
if userid in USERS:
return GROUPS.get(userid, [])
|
Copy the following into step02/templates/default_view.pt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<title>${page_title}</title>
</head>
<body>
<h1>${page_title}</h1>
<p>This is the home page, only visible to <code>group:editors</code></p>
<p><a href="edit">Edit</a> | <a href="/logout.html">Logout</a></p>
</body>
</html>
|
Copy the following into step02/templates/login.pt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal">
<head>
<title>${page_title}</title>
</head>
<body>
<h1>${page_title}</h1>
<p><em>${message}</em></p>
<form action="${url}" method="post">
<input type="hidden" name="came_from" value="${came_from}"/>
<input type="text" name="login" value="${login}"/><br/>
<input type="password" name="password"
value="${password}"/><br/>
<input type="submit" name="form.submitted" value="Log In"/>
</form>
</body>
</html>
|
$ python application.py
Open http://127.0.0.1:8080/ in your browser.
The login view has two decorators that are “stacked”. One makes sure that the login view gets shown when your URL is “/login.html”. (Normal stuff.)
The second @view_config has a context of HTTPForbidden. This is invoked when Pyramid raises the exception.
We redirect back to where you came from when logging in, which is a common pattern.