Compare commits
4 Commits
ceb6e13447
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
| bc75cac6cd | |||
| 47c264e8e1 | |||
| 4d0ed116dd | |||
| ccf31e4ef4 |
@@ -3,8 +3,6 @@ kind: Ingress
|
|||||||
metadata:
|
metadata:
|
||||||
name: django
|
name: django
|
||||||
namespace: vorgabenui
|
namespace: vorgabenui
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.middlewares: "vorgabenui-vorgabenui-rewrite@kubernetescrd"
|
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- host: vorgabenportal.knowyoursecurity.com
|
- host: vorgabenportal.knowyoursecurity.com
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
apiVersion: traefik.containo.us/v1alpha1
|
|
||||||
kind: Middleware
|
|
||||||
metadata:
|
|
||||||
name: vorgabenui-rewrite
|
|
||||||
namespace: vorgabenui
|
|
||||||
spec:
|
|
||||||
stripPrefix:
|
|
||||||
prefixes:
|
|
||||||
- "/"
|
|
||||||
266
pages/test_auth.py
Normal file
266
pages/test_auth.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationTest(TestCase):
|
||||||
|
"""Test login, logout, and password change functionality"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
self.test_user = User.objects.create_user(
|
||||||
|
username='testuser',
|
||||||
|
password='testpass123',
|
||||||
|
email='test@example.com'
|
||||||
|
)
|
||||||
|
self.staff_user = User.objects.create_user(
|
||||||
|
username='staffuser',
|
||||||
|
password='staffpass123',
|
||||||
|
email='staff@example.com'
|
||||||
|
)
|
||||||
|
self.staff_user.is_staff = True
|
||||||
|
self.staff_user.save()
|
||||||
|
|
||||||
|
def test_login_page_loads(self):
|
||||||
|
"""Test that login page loads correctly"""
|
||||||
|
response = self.client.get(reverse('login'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'Anmelden')
|
||||||
|
self.assertContains(response, 'Benutzername:')
|
||||||
|
self.assertContains(response, 'Passwort:')
|
||||||
|
|
||||||
|
def test_login_valid_credentials(self):
|
||||||
|
"""Test successful login with valid credentials"""
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'testpass123'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirect after login
|
||||||
|
self.assertRedirects(response, '/') # Should redirect to main page
|
||||||
|
|
||||||
|
# Check user is logged in
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertContains(response, 'testuser') # Username should appear in header
|
||||||
|
|
||||||
|
def test_login_invalid_credentials(self):
|
||||||
|
"""Test login with invalid credentials shows error"""
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'wrongpassword'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200) # Stay on login page
|
||||||
|
self.assertContains(response, 'Ihr Benutzername und Passwort stimmen nicht überein')
|
||||||
|
|
||||||
|
def test_login_empty_credentials(self):
|
||||||
|
"""Test login with empty credentials"""
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': '',
|
||||||
|
'password': ''
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200) # Stay on login page
|
||||||
|
# Django's form validation should handle this
|
||||||
|
|
||||||
|
def test_logout_functionality(self):
|
||||||
|
"""Test logout functionality"""
|
||||||
|
# First login
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
# Verify user is logged in
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertContains(response, 'testuser')
|
||||||
|
|
||||||
|
# Logout using POST
|
||||||
|
response = self.client.post(reverse('logout'))
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirect after logout
|
||||||
|
self.assertRedirects(response, '/') # Should redirect to main page
|
||||||
|
|
||||||
|
# Verify user is logged out
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertNotContains(response, 'testuser')
|
||||||
|
self.assertContains(response, 'Anmelden') # Should show login link
|
||||||
|
|
||||||
|
def test_logout_requires_post(self):
|
||||||
|
"""Test that logout requires POST method"""
|
||||||
|
# Login first
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
# Try GET logout (should fail with 405)
|
||||||
|
response = self.client.get(reverse('logout'))
|
||||||
|
self.assertEqual(response.status_code, 405) # Method Not Allowed
|
||||||
|
|
||||||
|
# User should still be logged in
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertContains(response, 'testuser')
|
||||||
|
|
||||||
|
def test_password_change_page_loads(self):
|
||||||
|
"""Test that password change page loads for authenticated users"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('password_change'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'Passwort ändern')
|
||||||
|
self.assertContains(response, 'Aktuelles Passwort:')
|
||||||
|
self.assertContains(response, 'Neues Passwort:')
|
||||||
|
self.assertContains(response, 'Neues Passwort bestätigen:')
|
||||||
|
|
||||||
|
def test_password_change_requires_authentication(self):
|
||||||
|
"""Test that password change page requires authentication"""
|
||||||
|
response = self.client.get(reverse('password_change'))
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirect to login
|
||||||
|
|
||||||
|
# Should redirect to login page
|
||||||
|
self.assertIn(reverse('login'), response.url)
|
||||||
|
|
||||||
|
def test_password_change_valid(self):
|
||||||
|
"""Test successful password change"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('password_change'), {
|
||||||
|
'old_password': 'testpass123',
|
||||||
|
'new_password1': 'newpass456',
|
||||||
|
'new_password2': 'newpass456'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirect after success
|
||||||
|
self.assertRedirects(response, '/') # Should redirect to main page
|
||||||
|
|
||||||
|
# Verify new password works
|
||||||
|
self.client.logout()
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'newpass456'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302) # Successful login
|
||||||
|
|
||||||
|
def test_password_change_wrong_old_password(self):
|
||||||
|
"""Test password change with wrong old password"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('password_change'), {
|
||||||
|
'old_password': 'wrongpassword',
|
||||||
|
'new_password1': 'newpass456',
|
||||||
|
'new_password2': 'newpass456'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200) # Stay on form page
|
||||||
|
self.assertContains(response, 'Bitte korrigieren Sie die Fehler unten')
|
||||||
|
|
||||||
|
def test_password_change_mismatched_new_passwords(self):
|
||||||
|
"""Test password change with mismatched new passwords"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('password_change'), {
|
||||||
|
'old_password': 'testpass123',
|
||||||
|
'new_password1': 'newpass456',
|
||||||
|
'new_password2': 'differentpass789'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 200) # Stay on form page
|
||||||
|
self.assertContains(response, 'Bitte korrigieren Sie die Fehler unten')
|
||||||
|
|
||||||
|
def test_password_change_same_as_old_password(self):
|
||||||
|
"""Test password change with same password as old"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('password_change'), {
|
||||||
|
'old_password': 'testpass123',
|
||||||
|
'new_password1': 'testpass123',
|
||||||
|
'new_password2': 'testpass123'
|
||||||
|
})
|
||||||
|
# Django's default validators don't prevent same password, so it should succeed
|
||||||
|
self.assertEqual(response.status_code, 302) # Redirect after success
|
||||||
|
self.assertRedirects(response, '/') # Should redirect to main page
|
||||||
|
|
||||||
|
def test_password_change_cancel_button(self):
|
||||||
|
"""Test password change cancel button"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.get(reverse('password_change'))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'Abbrechen')
|
||||||
|
# The cancel button should link to main page
|
||||||
|
self.assertContains(response, 'href="/"')
|
||||||
|
|
||||||
|
def test_user_menu_display_for_authenticated_user(self):
|
||||||
|
"""Test that user menu displays correctly for authenticated users"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'testuser') # Username in menu
|
||||||
|
self.assertContains(response, 'Passwort ändern') # Password change link
|
||||||
|
self.assertContains(response, 'Abmelden') # Logout link
|
||||||
|
self.assertNotContains(response, 'Anmelden') # Should not show login link
|
||||||
|
|
||||||
|
def test_login_link_display_for_anonymous_user(self):
|
||||||
|
"""Test that login link displays for anonymous users"""
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'Anmelden') # Should show login link
|
||||||
|
self.assertNotContains(response, 'testuser') # Should not show username
|
||||||
|
self.assertNotContains(response, 'Passwort ändern') # Should not show password change
|
||||||
|
self.assertNotContains(response, 'Abmelden') # Should not show logout
|
||||||
|
|
||||||
|
def test_staff_user_menu(self):
|
||||||
|
"""Test that staff users see appropriate menu"""
|
||||||
|
self.client.login(username='staffuser', password='staffpass123')
|
||||||
|
|
||||||
|
response = self.client.get('/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, 'staffuser') # Username in menu
|
||||||
|
self.assertContains(response, 'Passwort ändern') # Password change link
|
||||||
|
self.assertContains(response, 'Abmelden') # Logout link
|
||||||
|
|
||||||
|
def test_login_redirect_to_main_page(self):
|
||||||
|
"""Test that successful login redirects to main page"""
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'testpass123'
|
||||||
|
}, follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Should end up on main page
|
||||||
|
self.assertContains(response, 'Vorgaben Informatiksicherheit')
|
||||||
|
|
||||||
|
def test_logout_redirect_to_main_page(self):
|
||||||
|
"""Test that logout redirects to main page"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('logout'), follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Should end up on main page
|
||||||
|
self.assertContains(response, 'Vorgaben Informatiksicherheit')
|
||||||
|
# Should show login link for anonymous users
|
||||||
|
self.assertContains(response, 'Anmelden')
|
||||||
|
|
||||||
|
def test_password_change_redirect_to_main_page(self):
|
||||||
|
"""Test that successful password change redirects to main page"""
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
|
||||||
|
response = self.client.post(reverse('password_change'), {
|
||||||
|
'old_password': 'testpass123',
|
||||||
|
'new_password1': 'newpass456',
|
||||||
|
'new_password2': 'newpass456'
|
||||||
|
}, follow=True)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
# Should end up on main page
|
||||||
|
self.assertContains(response, 'Vorgaben Informatiksicherheit')
|
||||||
|
|
||||||
|
def test_csrf_token_present_in_forms(self):
|
||||||
|
"""Test that CSRF tokens are present in authentication forms"""
|
||||||
|
# Login form
|
||||||
|
response = self.client.get(reverse('login'))
|
||||||
|
self.assertContains(response, 'csrfmiddlewaretoken')
|
||||||
|
|
||||||
|
# Password change form
|
||||||
|
self.client.login(username='testuser', password='testpass123')
|
||||||
|
response = self.client.get(reverse('password_change'))
|
||||||
|
self.assertContains(response, 'csrfmiddlewaretoken')
|
||||||
|
|
||||||
|
def test_login_with_next_parameter(self):
|
||||||
|
"""Test login with next parameter for redirect"""
|
||||||
|
response = self.client.post(reverse('login'), {
|
||||||
|
'username': 'testuser',
|
||||||
|
'password': 'testpass123',
|
||||||
|
'next': '/dokumente/'
|
||||||
|
})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
# Should redirect to the specified next page
|
||||||
|
self.assertRedirects(response, '/dokumente/')
|
||||||
@@ -4,7 +4,6 @@ from django.utils import timezone
|
|||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from dokumente.models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Geltungsbereich, Dokumententyp, Thema
|
from dokumente.models import Dokument, Vorgabe, VorgabeKurztext, VorgabeLangtext, Geltungsbereich, Dokumententyp, Thema
|
||||||
from stichworte.models import Stichwort
|
from stichworte.models import Stichwort
|
||||||
from unittest.mock import patch
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@@ -67,24 +66,24 @@ class SearchViewTest(TestCase):
|
|||||||
"""Test POST request with valid search term"""
|
"""Test POST request with valid search term"""
|
||||||
response = self.client.post('/search/', {'q': 'Test'})
|
response = self.client.post('/search/', {'q': 'Test'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, 'Suchresultate für Test')
|
self.assertContains(response, 'Suchergebnisse')
|
||||||
|
|
||||||
def test_search_case_insensitive(self):
|
def test_search_case_insensitive(self):
|
||||||
"""Test that search is case insensitive"""
|
"""Test that search is case insensitive"""
|
||||||
# Search for lowercase
|
# Search for lowercase
|
||||||
response = self.client.post('/search/', {'q': 'test'})
|
response = self.client.post('/search/', {'q': 'test'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, 'Suchresultate für test')
|
self.assertContains(response, 'Suchergebnisse für "test"')
|
||||||
|
|
||||||
# Search for uppercase
|
# Search for uppercase
|
||||||
response = self.client.post('/search/', {'q': 'TEST'})
|
response = self.client.post('/search/', {'q': 'TEST'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, 'Suchresultate für TEST')
|
self.assertContains(response, 'Suchergebnisse für "TEST"')
|
||||||
|
|
||||||
# Search for mixed case
|
# Search for mixed case
|
||||||
response = self.client.post('/search/', {'q': 'TeSt'})
|
response = self.client.post('/search/', {'q': 'TeSt'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, 'Suchresultate für TeSt')
|
self.assertContains(response, 'Suchergebnisse für "TeSt"')
|
||||||
|
|
||||||
def test_search_in_kurztext(self):
|
def test_search_in_kurztext(self):
|
||||||
"""Test search in Kurztext content"""
|
"""Test search in Kurztext content"""
|
||||||
@@ -114,7 +113,7 @@ class SearchViewTest(TestCase):
|
|||||||
"""Test search with no results"""
|
"""Test search with no results"""
|
||||||
response = self.client.post('/search/', {'q': 'NichtVorhanden'})
|
response = self.client.post('/search/', {'q': 'NichtVorhanden'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, 'Keine Resultate für "NichtVorhanden"')
|
self.assertContains(response, 'Keine Ergebnisse gefunden')
|
||||||
|
|
||||||
def test_search_expired_vorgabe_not_included(self):
|
def test_search_expired_vorgabe_not_included(self):
|
||||||
"""Test that expired Vorgaben are not included in results"""
|
"""Test that expired Vorgaben are not included in results"""
|
||||||
@@ -160,8 +159,8 @@ class SearchViewTest(TestCase):
|
|||||||
"""Test that HTML tags are stripped from search input"""
|
"""Test that HTML tags are stripped from search input"""
|
||||||
response = self.client.post('/search/', {'q': '<script>alert("xss")</script>Test'})
|
response = self.client.post('/search/', {'q': '<script>alert("xss")</script>Test'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# Should search for "alert('xss')Test" after HTML tag removal
|
# Should search for "alert("xss")Test" after HTML tag removal
|
||||||
self.assertContains(response, 'Suchresultate für alert("xss")Test')
|
self.assertContains(response, 'Suchergebnisse für "alert')
|
||||||
|
|
||||||
def test_search_invalid_characters_validation(self):
|
def test_search_invalid_characters_validation(self):
|
||||||
"""Test validation for invalid characters"""
|
"""Test validation for invalid characters"""
|
||||||
@@ -206,7 +205,7 @@ class SearchViewTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# The input should be preserved (escaped) in the form
|
# The input should be preserved (escaped) in the form
|
||||||
# Since HTML tags are stripped, we expect "Test" to be searched
|
# Since HTML tags are stripped, we expect "Test" to be searched
|
||||||
self.assertContains(response, 'Suchresultate für Test')
|
self.assertContains(response, 'Suchergebnisse für "Test"')
|
||||||
|
|
||||||
def test_search_xss_prevention_in_results(self):
|
def test_search_xss_prevention_in_results(self):
|
||||||
"""Test that search terms are escaped in results to prevent XSS"""
|
"""Test that search terms are escaped in results to prevent XSS"""
|
||||||
@@ -218,15 +217,14 @@ class SearchViewTest(TestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# The script tag should be escaped in the output
|
# The script tag should be escaped in the output
|
||||||
# Note: This depends on how the template renders the content
|
# Note: This depends on how the template renders the content
|
||||||
self.assertContains(response, 'Suchresultate für term')
|
self.assertContains(response, 'Suchergebnisse für "term"')
|
||||||
|
|
||||||
@patch('pages.views.pprint.pp')
|
def test_search_result_structure(self):
|
||||||
def test_search_result_logging(self, mock_pprint):
|
"""Test that search results have expected structure"""
|
||||||
"""Test that search results are logged for debugging"""
|
|
||||||
response = self.client.post('/search/', {'q': 'Test'})
|
response = self.client.post('/search/', {'q': 'Test'})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
# Verify that pprint.pp was called with the result
|
# Verify the results page is rendered with correct structure
|
||||||
mock_pprint.assert_called_once()
|
self.assertContains(response, 'Suchergebnisse für "Test"')
|
||||||
|
|
||||||
def test_search_multiple_documents(self):
|
def test_search_multiple_documents(self):
|
||||||
"""Test search across multiple documents"""
|
"""Test search across multiple documents"""
|
||||||
|
|||||||
Reference in New Issue
Block a user