Browse Source

added login and logout of users

master
Holger Frey 7 years ago
parent
commit
290b864d17
  1. 39
      ordr2/templates/account/login.jinja2
  2. 6
      ordr2/templates/layout.jinja2
  3. 40
      ordr2/views/account.py
  4. 40
      tests/_functional/__init__.py
  5. 81
      tests/_functional/account.py
  6. 38
      tests/_functional/layout_and_pages.py

39
ordr2/templates/account/login.jinja2

@ -0,0 +1,39 @@
{% extends "ordr2:templates/layout.jinja2" %}
{% block title %} Ordr | Login Failed {% endblock title %}
{% block content %}
<div class="row justify-content-center">
<div class="col-8">
<h1>Try again…</h1>
<p class="alert alert-danger">You entered the wrong username or password.</p>
<form action="{{ request.resource_url(request.root, 'account', 'login') }}" method="POST" id="login-form">
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}">
<div class="form-group row">
<label for="username" class="col-2">Username</label>
<div class="col-6">
<input name="username" id="username" type="text" class="form-control">
</div>
</div>
<div class="form-group row">
<label for="password" class="col-2">Password</label>
<div class="col-6">
<input name="password" id="password>" type="password" class="form-control">
</div>
</div>
<div class="form-group row">
<div class="col-2"></div>
<div class="col-6">
<button type="submit" class="btn btn-sm btn-primary">Log in</button>
</div>
</div>
</form>
<p>
If you forgot your password you can <a href="{{ request.resource_url(context, 'forgot-password') }}">reset it</a>.
</p>
</div>
</div>
{% endblock content %}

6
ordr2/templates/layout.jinja2

@ -33,7 +33,9 @@
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item {{ 'active' if request.context.nav_section == 'orders' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'orders') }}">Orders</a></li> <li class="nav-item {{ 'active' if request.context.nav_section == 'orders' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'orders') }}">Orders</a></li>
<li class="nav-item {{ 'active' if request.context.nav_section == 'root' and request.view_name == 'faq' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'faq') }}">FAQs</a></li> <li class="nav-item {{ 'active' if request.context.nav_section == 'root' and request.view_name == 'faq' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'faq') }}">FAQs</a></li>
<li class="nav-item {{ 'active' if request.context.nav_section == 'admin' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'admin') }}">Admin</a></li> {% if request.user.role.name == 'ADMIN' %}
<li class="nav-item {{ 'active' if request.context.nav_section == 'admin' }}"><a class="nav-link" href="{{ request.resource_url(request.root, 'admin') }}">Admin</a></li>
{% endif %}
</ul> </ul>
<!-- account stuff on the right --> <!-- account stuff on the right -->
@ -60,7 +62,7 @@
</ul> </ul>
<!-- login form on the right --> <!-- login form on the right -->
<form action="{{ request.resource_url(request.root, 'account', 'login') }}" method="POST" class="login-form form-inline"> <form action="{{ request.resource_url(request.root, 'account', 'login') }}" method="POST" id="login-form" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}">
<input name="username" type="text" placeholder="Username" class="form-control form-control-sm"> <input name="username" type="text" placeholder="Username" class="form-control form-control-sm">
<input name="password" type="password" placeholder="Password" class="form-control form-control-sm"> <input name="password" type="password" placeholder="Password" class="form-control form-control-sm">

40
ordr2/views/account.py

@ -1,7 +1,11 @@
''' account registration, login, logout and settings ''' ''' account registration, login, logout and settings '''
from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget
from pyramid.view import view_config from pyramid.view import view_config
from ordr2.models import User
@view_config( @view_config(
context='ordr2:resources.account.AccountResource', context='ordr2:resources.account.AccountResource',
@ -12,3 +16,39 @@ from pyramid.view import view_config
def register(context, request): def register(context, request):
''' the new user registraion page ''' ''' the new user registraion page '''
return {} return {}
@view_config(
context='ordr2:resources.account.AccountResource',
name='login',
permission='login',
request_method='POST',
renderer='ordr2:templates/account/login.jinja2'
)
def login(context, request):
''' loging in a user '''
username = request.POST.get('username')
password = request.POST.get('password')
# Form validation is not done for login forms,
# either the data represents a user or not.
user = request.dbsession.query(User).filter_by(username=username).first()
if user is not None:
if user.is_active and user.check_password(password):
headers = remember(request, user.id)
return HTTPFound(
request.resource_path(request.root, 'orders'),
headers=headers
)
return {}
@view_config(
context='ordr2:resources.account.AccountResource',
name='logout',
permission='logout'
)
def logout(context, request):
''' log out an user '''
headers = forget(request)
return HTTPFound(request.resource_url(request.root), headers=headers)

40
tests/_functional/__init__.py

@ -5,19 +5,41 @@ import pytest
import transaction import transaction
import webtest import webtest
from .. import APP_SETTINGS, create_users from .. import APP_SETTINGS, create_users, get_user
# some path mangling to get the path to passlib.ini
currrent_dir = os.path.dirname(__file__)
ordr2_dir = os.path.dirname(os.path.dirname(currrent_dir))
passlib_config_path = os.path.join(ordr2_dir, 'passlib.ini')
WEBTEST_SETTINGS = APP_SETTINGS.copy() WEBTEST_SETTINGS = APP_SETTINGS.copy()
# WEBTEST_SETTINGS.update({ }) # WEBTEST_SETTINGS.update({ })
class CustomTestApp(webtest.TestApp):
''' adds login and logout methods to webtest.TestApp '''
def reset(self):
''' logs out any user and resets the state of the application '''
self.logout()
super().reset()
def logout(self):
''' perfomes a logout of a user '''
self.get('/account/logout')
def login(self, role_name):
''' perfomes a logs in of a user '''
# do a logout first, testapp fixture scope is on module level
self.logout()
user = get_user(role_name)
response = self.get('/')
login_form = response.forms['login-form']
login_form.set('username', user.username)
login_form.set('password', user.first_name)
login_form.submit()
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def testapp(): def testapp():
''' fixture for using webtest ''' ''' fixture for using webtest '''
@ -26,7 +48,7 @@ def testapp():
from ordr2 import main from ordr2 import main
app = main({}, **WEBTEST_SETTINGS) app = main({}, **WEBTEST_SETTINGS)
testapp = webtest.TestApp(app) testapp = CustomTestApp(app)
session_factory = app.registry['dbsession_factory'] session_factory = app.registry['dbsession_factory']
engine = session_factory.kw['bind'] engine = session_factory.kw['bind']
@ -37,6 +59,6 @@ def testapp():
dbsession = get_tm_session(session_factory, transaction.manager) dbsession = get_tm_session(session_factory, transaction.manager)
create_users(dbsession) create_users(dbsession)
yield testapp yield testapp
Base.metadata.drop_all(engine) Base.metadata.drop_all(engine)

81
tests/_functional/account.py

@ -1,6 +1,9 @@
''' tests for the common layout and simple (static)''' ''' tests for the common layout and simple (static)'''
import pytest
from . import testapp from . import testapp
from .. import get_user
def test_account_register_unauthenticated(testapp): def test_account_register_unauthenticated(testapp):
@ -18,3 +21,81 @@ def test_account_register_unauthenticated(testapp):
assert 'active' in li_two['class'] assert 'active' in li_two['class']
assert li_two.find('a').text == 'Register' assert li_two.find('a').text == 'Register'
@pytest.mark.parametrize('role_name', ['user', 'purchaser', 'admin', ])
def test_account_login_for_active_users(testapp, role_name):
''' check if user login works '''
testapp.reset()
user = get_user(role_name)
root = testapp.get('/')
login_form = root.forms['login-form']
login_form.set('username', user.username)
login_form.set('password', user.first_name)
response = login_form.submit()
# a login leads to a redirect
assert response.status == '302 Found'
assert response.location.endswith('/orders')
# the layout should reflect the login
response = testapp.get('/faq')
assert '<!-- user is logged in -->' in response
assert 'id="login-form"' not in response
assert 'Logged in as <span>{}</span>'.format(user.username) in response
@pytest.mark.parametrize('role_name', ['unvalidated', 'new', 'inactive'])
def test_account_login_for_inactive_users(testapp, role_name):
''' check if user login works '''
testapp.reset()
user = get_user(role_name)
root = testapp.get('/')
login_form = root.forms['login-form']
login_form.set('username', user.username)
login_form.set('password', user.first_name)
response = login_form.submit()
assert '<!-- No logged in user -->' in response
assert 'id="login-form' in response
assert 'Logged in as <span>{}</span>'.format(user.username) not in response
assert 'You entered the wrong username or password' in response
@pytest.mark.parametrize(
'username, password', [
('EricIdle', 'wrong password'),
('unknown user', 'Eric'),
('unknown user', 'unknown password')
]
)
def test_account_login_fails(testapp, username, password):
''' check if user login works '''
testapp.reset()
root = testapp.get('/')
login_form = root.forms['login-form']
login_form.set('username', username)
login_form.set('password', password)
response = login_form.submit()
assert '<!-- No logged in user -->' in response
assert 'id="login-form' in response
assert 'Logged in as <span>{}</span>'.format(username) not in response
assert 'You entered the wrong username or password' in response
def test_account_login_only_by_post(testapp):
testapp.reset()
def test_account_logout_works(testapp):
''' check if a user can log out '''
testapp.reset()
testapp.login('user')
response = testapp.get('/account/logout').follow()
assert '<!-- No logged in user -->' in response
assert 'id="login-form' in response

38
tests/_functional/layout_and_pages.py

@ -3,6 +3,8 @@
from . import testapp from . import testapp
# tests for layout template
def test_layout_unauthenticated_user(testapp): def test_layout_unauthenticated_user(testapp):
''' test the layout template elements for unauthenticated users ''' test the layout template elements for unauthenticated users
@ -13,8 +15,42 @@ def test_layout_unauthenticated_user(testapp):
response = testapp.get('/faq') response = testapp.get('/faq')
assert '<!-- No logged in user -->' in response assert '<!-- No logged in user -->' in response
assert 'class="login-form' in response assert 'id="login-form' in response
def test_layout_authenticated_user(testapp):
''' test the layout template elements for unauthenticated users
The '/faq' path is used; '/' will redirect on authenticated users
'''
testapp.reset()
testapp.login('user')
response = testapp.get('/faq')
assert '<!-- user is logged in -->' in response
assert 'id="login-form"' not in response
assert 'Admin</a></li>' not in response
assert 'Logged in as <span>TerryGilliam</span>' in response
def test_layout_authenticated_admin(testapp):
''' test the layout template elements for unauthenticated users
The '/faq' path is used; '/' will redirect on authenticated users
'''
testapp.reset()
testapp.login('admin')
response = testapp.get('/faq')
assert '<!-- user is logged in -->' in response
assert 'id="login-form"' not in response
assert 'Admin</a></li>' in response
assert 'Logged in as <span>TerryJones</span>' in response
# tests for simple pages
def test_page_welcome_unauthenticated(testapp): def test_page_welcome_unauthenticated(testapp):
''' test the welcome page for a unauthenticated user ''' ''' test the welcome page for a unauthenticated user '''