finish liveservertests

This commit is contained in:
Tom Irgang 2023-04-27 11:09:41 +02:00
parent f1d65e2bd6
commit 69aa578ad2
8 changed files with 272 additions and 24 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
.env
**/data.json
rc_hip/static
rc_hip/static/**

View file

@ -12,6 +12,9 @@ logger = logging.getLogger(__name__)
def generate_username(email):
"""
Generate a valid user name form the mail address.
"""
# Using Python 3 and Django 1.11+, usernames can contain alphanumeric
# (ascii and unicode), _, @, +, . and - characters. So we normalize
# it and slice at 150 characters.
@ -19,19 +22,29 @@ def generate_username(email):
def create_repapp_user(user):
"""
Create the RepApp user objects for the new user.
If a user logs in using OIDC it is a member of the Repair-Café, so it can be
either a Reparateur or an Organisator. Organisators have access to the private
data form the guests and must be nominated by an admin.
"""
organisator = Organisator.objects.filter(mail=user.email).first()
if not organisator:
# no organisator, create or update reparateur
reparateur = Reparateur.objects.filter(mail=user.email).first()
if not reparateur:
# create new reparateur
reparateur = Reparateur(
name=user.username,
mail=user.email,
)
reparateur.save()
else:
# update name of existing reparateur
reparateur.name = user.username
reparateur.save()
else:
# update name of organisator
organisator.name = user.username
organisator.save()
@ -74,15 +87,17 @@ class KeycloakOIDCAB(OIDCAuthenticationBackend):
def create_user(self, claims):
user = super(KeycloakOIDCAB, self).create_user(claims)
logger.debug(f'Create user {user.email}')
logger.info('Create user %s' % user.email)
fallback_name = generate_username(user.email)
try:
user.username = claims.get(
'preferred_username', generate_username(user.email))
user.username = claims.get('preferred_username', fallback_name)
user.save()
except Exception as exception:
logger.error(exception)
user.username = generate_username(user.email)
logger.warning(
'Update username failed! Using fallback name %s.' % fallback_name)
user.username = fallback_name
user.save()
logger.debug(f'Updated username {user.username}')

View file

@ -7,5 +7,52 @@
"address": "Patersholzer Weg 19, Hilpoltstein",
"event_date": "2023-05-13"
}
},
{
"model": "repapp.customuser",
"pk": 3,
"fields": {
"password": "pbkdf2_sha256$600000$O70jrsL2vmSssirJPvWBMw$lbvFy/OI7mUfz26Qvh+bxnsp19v+xsETJydvsgLxE4E=",
"last_login": "2023-04-25T18:47:07.685Z",
"is_superuser": false,
"username": "tom",
"first_name": "",
"last_name": "",
"is_staff": false,
"is_active": true,
"date_joined": "2023-04-25T07:10:40.972Z",
"email": "thomas@irgang.eu",
"groups": [],
"user_permissions": []
}
},
{
"model": "repapp.guest",
"pk": 4,
"fields": {
"identifier": "272623d8b634cef82ba1c8981593cd827a373226c492b328671d31418a19bcc1",
"name": "Thomas",
"phone": "012345678",
"residence": "Allersberg",
"mail": "thomas@irgang.eu",
"user": 3
}
},
{
"model": "repapp.device",
"pk": 2,
"fields": {
"identifier": "5f3d413409ca9c90c99540fd5677cc1147156f100d7bd29d3a1c1940e5f2c6ac",
"date": "2023-04-25",
"device": "Test Gerät",
"manufacturer": "Test Hersteller",
"error": "Fehler",
"follow_up": false,
"device_picture": "",
"type_plate_picture": "",
"confirmed": true,
"guest": 4,
"cafe": 1
}
}
]

View file

@ -3,25 +3,42 @@ Django forms for RepApp.
"""
from django import forms
from django.core.exceptions import ValidationError
from crispy_forms.layout import Field
class HoneypotField(forms.BooleanField):
EMPTY_VALUES = (None, '')
class HoneypotWidget(forms.TextInput):
"""
Simple honeypot field.
This widget shows a by CSS hidden input field. If this form gets manipulated
by a bot the form is rejected.
"""
default_widget = forms.CheckboxInput(
{'style': 'display:none !important;', 'tabindex': '-1', 'autocomplete': 'off'})
is_hidden = True
def __init__(self, *args, **kwargs):
kwargs.setdefault('widget', HoneypotField.default_widget)
kwargs['required'] = False
super().__init__(*args, **kwargs)
def __init__(self, attrs=None, html_comment=False, *args, **kwargs):
self.html_comment = html_comment
super(HoneypotWidget, self).__init__(attrs, *args, **kwargs)
if not 'class' in self.attrs:
self.attrs['style'] = 'display:none'
def render(self, *args, **kwargs):
value = super(HoneypotWidget, self).render(*args, **kwargs)
if self.html_comment:
value = '<!-- %s -->' % value
return value
class HoneypotField(forms.Field):
"""
This widget implements a simple honey pot field as spam protection.
"""
widget = HoneypotWidget
def clean(self, value):
if cleaned_value := super().clean(value):
raise ValidationError('')
else:
return cleaned_value
if self.initial in EMPTY_VALUES and value in EMPTY_VALUES or value == self.initial:
return value
raise ValidationError('Anti-spam field changed in value.')
class RegisterDevice(forms.Form):

View file

@ -1,12 +1,21 @@
import time
import os
import datetime
import random
from pathlib import Path
from hashlib import sha256
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.contrib.auth import get_user_model
from selenium.webdriver.common.by import By
# from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.firefox.webdriver import WebDriver
from .models import CustomUser, Guest, OneTimeLogin
class WorkflowTests(StaticLiveServerTestCase):
"""
Tests for workflows.
"""
fixtures = ["cafe-data.json"]
@classmethod
@ -20,7 +29,35 @@ class WorkflowTests(StaticLiveServerTestCase):
cls.selenium.quit()
super().tearDownClass()
def setUp(self):
super().setUp()
self.password = "ATestPassword"
user = CustomUser.objects.get(pk=3)
user.set_password(self.password)
user.save()
self.user = user
print(f'Password of user {user.email} updated to {self.password}.')
url = "/guest/profile/"
secret = sha256(
f'{user.email}{url}{datetime.datetime.now()}{random.randint(0,9999999)}'.encode(
'utf-8')
).hexdigest()
self.secret = secret
secret_hash = sha256(secret.encode('utf-8')).hexdigest()
one_time_login = OneTimeLogin(
secret=secret_hash,
user=user,
url=url,
)
one_time_login.save()
self.one_time_login = one_time_login
def test_register_device(self):
"""
Test device registration flows.
"""
# Open landing page
self.selenium.get(f"{self.live_server_url}/")
# Click on a register link
@ -110,3 +147,55 @@ class WorkflowTests(StaticLiveServerTestCase):
# Guest is known, no guest registration needed.
self.assertTrue("confirm" in self.selenium.current_url)
self.assertTrue("Anmeldung erfolgreich!" in self.selenium.page_source)
def test_login_and_view_profile(self):
"""
Test login and profile page.
"""
# Open login form
self.selenium.get(f"{self.live_server_url}/accounts/login/")
# Enter login data
# Enter guest mail address
username = self.selenium.find_element(By.NAME, "username")
username.send_keys(self.user.email)
# Enter password
password = self.selenium.find_element(By.NAME, "password")
password.send_keys(self.password)
time.sleep(1)
# Submit form
self.selenium.find_element(
By.XPATH, '//button[@type="submit"]').click()
time.sleep(1)
self.selenium.get(f"{self.live_server_url}/guest/profile/")
time.sleep(1)
self.assertTrue(self.user.email in self.selenium.page_source)
self.assertTrue("/guest/profile/" in self.selenium.current_url)
time.sleep(1)
def test_one_time_login_and_view_device(self):
"""
Test one time login and device page.
"""
# Open login form
self.selenium.get(
f"{self.live_server_url}/onetimelogin/{self.secret}/")
time.sleep(1)
self.assertTrue(self.user.email in self.selenium.page_source)
self.assertTrue("Login erfolgreich" in self.selenium.page_source)
self.assertTrue("/guest/profile/" in self.selenium.current_url)
time.sleep(1)
self.selenium.get(
f"{self.live_server_url}/device/5f3d413409ca9c90c99540fd5677cc1147156f100d7bd29d3a1c1940e5f2c6ac/")
time.sleep(1)
self.assertTrue("Test Gerät" in self.selenium.page_source)
self.assertTrue("Test Hersteller" in self.selenium.page_source)
self.assertTrue("/device/" in self.selenium.current_url)
time.sleep(1)
def test_member_login(self):
self.selenium.get(
f"{self.live_server_url}/member/login/")
time.sleep(1)
self.selenium.find_element(By.CLASS_NAME, 'login_link').click()
time.sleep(1)
self.assertTrue('sso.makes-hacks-hip.de' in self.selenium.current_url)

View file

@ -20,7 +20,7 @@
<button type="submit" class="btn btn-primary">Abmelden</button>
</form>
{% else %}
<a href="{% url 'oidc_authentication_init' %}">Anmelden mit Makes-Hacks-Hip</a>
<a class="login_link" href="{% url 'oidc_authentication_init' %}">Anmelden mit Makes-Hacks-Hip</a>
{% endif %}
</div>
{% endblock content %}

View file

@ -10,8 +10,8 @@ from django.test import TestCase
from django.test import Client
from django.urls import reverse
from .backends import OneTimeLoginBackend, EmailBackend
from .models import OneTimeLogin, CustomUser, Cafe
from .backends import OneTimeLoginBackend, EmailBackend, create_repapp_user
from .models import (OneTimeLogin, CustomUser, Cafe, Organisator, Reparateur)
class OneTimeLoginTest(TestCase):
@ -100,11 +100,89 @@ class EmailLoginTest(TestCase):
self.assertEqual(result, None)
class UtilsTest(TestCase):
class BackendsTest(TestCase):
"""
Test for utility functions.
Test for backend utility functions.
"""
pass
def setUp(self):
organisator = Organisator(
name="Organisator Name",
mail="orga@example.com",
)
organisator.save()
self.organisator = organisator
reparateur = Reparateur(
name="Reparateur Name",
mail="repa@example.com",
)
reparateur.save()
self.reparateur = reparateur
def test_create_reparateur(self):
"""
Ensure that a new reparateur is created.
"""
user = CustomUser(
username="Other Reparateur",
email="repa2@example.com",
)
user.save()
create_repapp_user(user)
reparateur = Reparateur.objects.filter(mail=user.email).first()
self.assertIsNotNone(reparateur)
self.assertEqual(reparateur.name, user.username)
def test_update_reparateur(self):
"""
Ensure that the reparateur name gets updated.
"""
user = CustomUser(
username="First Reparateur",
email="repa@example.com",
)
user.save()
create_repapp_user(user)
reparateur = Reparateur.objects.filter(mail=user.email).first()
self.assertIsNotNone(reparateur)
self.assertEqual(reparateur.name, user.username)
def test_update_organisator(self):
"""
Ensure that the organisator name gets updated.
"""
user = CustomUser(
username="The Organisator",
email="orga@example.com",
)
user.save()
create_repapp_user(user)
organisator = Organisator.objects.filter(mail=user.email).first()
self.assertIsNotNone(organisator)
self.assertEqual(organisator.name, user.username)
class FormsTest(TestCase):
"""
Test for RepApp form features.
"""
fixtures = ["cafe-data.json"]
def test_from_protection(self):
client = Client(enforce_csrf_checks=True)
response = client.get(reverse('register_device', kwargs={
'cafe': 1,
}))
# ensure honeypot field is available
self.assertContains(
response, 'type="text" name="accept_agb" style="display:none"')
# ensure csrf is available
self.assertContains(response, 'csrfmiddlewaretoken')
class ViewsTest(TestCase):

View file

@ -22,10 +22,11 @@ urlpatterns = [
views.device_view, name="view_device"),
path("guest/profile/",
views.profile, name="guest_profile"),
path("onetimelogin/<str:secret>/",
views.one_time_login, name="one_time_login"),
path("member/login/",
views.member_login, name="member_login"),
path("cron", views.cron, name="cron"),
path("process_mails", views.process_mails, name="process_mails"),
path("onetimelogin/<str:secret>/",
views.one_time_login, name="one_time_login"),
]