From 8ebf90f194a3f4c066ea95f1afc2b725cc704fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Denis=20Krienb=C3=BChl?= Date: Tue, 17 Mar 2015 10:58:24 +0100 Subject: [PATCH] Adds basic login/logout views --- onegov/town/templates/form.pt | 8 +++ onegov/town/templates/macros.pt | 16 +++++ onegov/town/tests/conftest.py | 8 ++- onegov/town/tests/test_views.py | 40 +++++++++++ onegov/town/views/__init__.py | 0 onegov/town/{view.py => views/homepage.py} | 3 +- onegov/town/views/login.py | 84 ++++++++++++++++++++++ tox.ini | 1 + 8 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 onegov/town/templates/form.pt create mode 100644 onegov/town/views/__init__.py rename onegov/town/{view.py => views/homepage.py} (84%) create mode 100644 onegov/town/views/login.py diff --git a/onegov/town/templates/form.pt b/onegov/town/templates/form.pt new file mode 100644 index 0000000..f5c8ed6 --- /dev/null +++ b/onegov/town/templates/form.pt @@ -0,0 +1,8 @@ +
+ + ${title} + + +
+ +
\ No newline at end of file diff --git a/onegov/town/templates/macros.pt b/onegov/town/templates/macros.pt index 8646651..917f68b 100644 --- a/onegov/town/templates/macros.pt +++ b/onegov/town/templates/macros.pt @@ -49,4 +49,20 @@ + +
+ + + +
+ + + + + ${error} + + + \ No newline at end of file diff --git a/onegov/town/tests/conftest.py b/onegov/town/tests/conftest.py index 4278e94..a719106 100644 --- a/onegov/town/tests/conftest.py +++ b/onegov/town/tests/conftest.py @@ -7,6 +7,7 @@ from morepath import setup from onegov.core.orm import Base, SessionManager from onegov.town.initial_content import add_initial_content +from onegov.user import UserCollection from testing.postgresql import Postgresql from uuid import uuid4 @@ -45,11 +46,16 @@ def town_app(postgres_server_url): app.namespace = 'test_' + uuid4().hex app.configure_application( dsn=postgres_server_url, - filestorage='fs.memoryfs.MemoryFS' + filestorage='fs.memoryfs.MemoryFS', + identity_secure=False ) app.set_application_id(app.namespace + '/' + 'test') add_initial_content(app.session(), 'Govikon') + + users = UserCollection(app.session()) + users.add('admin@example.org', 'hunter2', 'admin') + transaction.commit() yield app diff --git a/onegov/town/tests/test_views.py b/onegov/town/tests/test_views.py index 96772b9..c04b120 100644 --- a/onegov/town/tests/test_views.py +++ b/onegov/town/tests/test_views.py @@ -2,6 +2,7 @@ from mock import patch from morepath import setup +from webtest import TestApp as Client @patch('morepath.directive.register_view') @@ -19,3 +20,42 @@ def test_view_permissions(register_view): if module.startswith('onegov.town') and permission is None: assert permission is not None, ( 'view {}.{} has no permission'.format(module, view.__name__)) + + +def test_view_login(town_app): + + client = Client(town_app) + + assert client.get('/logout', expect_errors=True).status_code == 403 + + response = client.get('/login') + assert response.status_code == 200 + assert "Email Address" in response.text + assert "Password" in response.text + + assert client.cookies == {} + assert client.get('/logout', expect_errors=True).status_code == 403 + + response.form.set('email', 'admin@example.org') + response = response.form.submit() + assert response.status_code == 200 + assert "Email Address" in response.text + assert "Password" in response.text + assert "This field is required." in response.text + + assert client.cookies == {} + assert client.get('/logout', expect_errors=True).status_code == 403 + + response.form.set('email', 'admin@example.org') + response.form.set('password', 'hunter2') + response = response.form.submit() + assert response.status_code == 302 + + assert 'userid' in client.cookies + assert 'role' in client.cookies + assert 'application_id' in client.cookies + + assert client.get('/logout').status_code == 302 + assert client.cookies == {} + + assert client.get('/logout', expect_errors=True).status_code == 403 diff --git a/onegov/town/views/__init__.py b/onegov/town/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/onegov/town/view.py b/onegov/town/views/homepage.py similarity index 84% rename from onegov/town/view.py rename to onegov/town/views/homepage.py index a82cdfa..c68d1e1 100644 --- a/onegov/town/view.py +++ b/onegov/town/views/homepage.py @@ -1,4 +1,4 @@ -""" Contains the view handling code for onegov.town. """ +""" The onegov town homepage. """ from onegov.core.security import Public from onegov.town import _ @@ -9,6 +9,7 @@ @TownApp.html(model=Town, template='town.pt', permission=Public) def view_town(self, request): + """ Renders the town's homepage. """ return { 'layout': DefaultLayout(self, request), 'title': _(u'Welcome to ${town}', mapping={'town': self.name}) diff --git a/onegov/town/views/login.py b/onegov/town/views/login.py new file mode 100644 index 0000000..28560be --- /dev/null +++ b/onegov/town/views/login.py @@ -0,0 +1,84 @@ +""" The login/logout views. """ + +import morepath + +from onegov.core.security import Public, Private +from onegov.form import Form +from wtforms import StringField, PasswordField, validators +from onegov.town import _ +from onegov.town.app import TownApp +from onegov.town.layout import DefaultLayout +from onegov.town.model import Town +from onegov.user import UserCollection + + +class LoginForm(Form): + """ Defines the login form for onegov town. """ + + email = StringField(_(u'Email Address'), [validators.InputRequired()]) + password = PasswordField(_(u'Password'), [validators.InputRequired()]) + + def get_identity(self, request): + """ Returns the identity if the username and password match. If they + don't match, None is returned. + + """ + users = UserCollection(request.app.session()) + user = users.by_username_and_password( + self.email.data, self.password.data + ) + + if user is None: + return None + else: + return morepath.Identity( + userid=user.username, + role=user.role, + application_id=request.app.application_id + ) + + +@TownApp.html( + model=Town, name='login', template='form.pt', permission=Public, + request_method='GET') +def view_login(self, request): + return handle_login(self, request) + + +@TownApp.html( + model=Town, name='login', template='form.pt', permission=Public, + request_method='POST') +def view_post_login(self, request): + return handle_login(self, request) + + +def handle_login(self, request): + """ Handles the GET and POST login requests. """ + + form = LoginForm(request.POST) + form.action = request.link(self, name='login') + + if form.submitted(request): + identity = form.get_identity(request) + + if identity is not None: + response = morepath.redirect(request.link(self)) + morepath.remember_identity(response, request, identity) + return response + + return { + 'layout': DefaultLayout(self, request), + 'title': _(u'Login to ${town}', mapping={'town': self.name}), + 'form': form + } + + +@TownApp.html( + model=Town, name='logout', permission=Private, + request_method='GET') +def view_logout(self, request): + """ Handles the logout requests. """ + + response = morepath.redirect(request.link(self)) + morepath.forget_identity(response, request) + return response diff --git a/tox.ini b/tox.ini index 6a6d9bb..db58303 100644 --- a/tox.ini +++ b/tox.ini @@ -7,6 +7,7 @@ dependencies = git+git://github.com/seantis/more.itsdangerous.git#egg=more.itsda git+git://github.com/OneGov/onegov.server.git#egg=onegov.server git+git://github.com/OneGov/onegov.core.git#egg=onegov.core git+git://github.com/OneGov/onegov.page.git#egg=onegov.page + git+git://github.com/OneGov/onegov.form.git#egg=onegov.form git+git://github.com/OneGov/onegov.user.git#egg=onegov.user git+git://github.com/OneGov/onegov.foundation.git#egg=onegov.foundation git+git://github.com/morepath/morepath.git#egg=morepath