Commit 17e4f8aa authored by MariaV's avatar MariaV
Browse files

Merge branch 'better_login_form' into 'master'

String Version of Login Form

See merge request !83
parents 6c959822 eb17c3a2
from django import forms
from django.forms import ModelForm, modelformset_factory, BaseModelFormSet
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
import gitlab
import random
from django.core.exceptions import ValidationError
from .models import UserIdentifier, Project, Issue, Note, GitlabAccountRequest
# Initialize GitLab Object
......@@ -12,26 +13,92 @@ gl = gitlab.Gitlab(settings.GITLAB_URL, private_token=settings.GITLAB_SECRET_TOK
class LoginForm(forms.Form):
"""A form that allows users to enter in their keycodes to login."""
word_1 = forms.CharField(max_length=9)
word_2 = forms.CharField(max_length=9)
word_3 = forms.CharField(max_length=9)
word_4 = forms.CharField(max_length=9)
word_5 = forms.CharField(max_length=9)
word_6 = forms.CharField(max_length=9)
def join_words(self):
"""Pull cleaned data from form and join into code_phrase"""
word_list = []
word_list.append(self.cleaned_data['word_1'])
word_list.append(self.cleaned_data['word_2'])
word_list.append(self.cleaned_data['word_3'])
word_list.append(self.cleaned_data['word_4'])
word_list.append(self.cleaned_data['word_5'])
word_list.append(self.cleaned_data['word_6'])
word_1 = forms.CharField(max_length=9, required=False)
word_2 = forms.CharField(max_length=9, required=False)
word_3 = forms.CharField(max_length=9, required=False)
word_4 = forms.CharField(max_length=9, required=False)
word_5 = forms.CharField(max_length=9, required=False)
word_6 = forms.CharField(max_length=9, required=False)
login_string = forms.CharField(max_length=255, required=False)
# def join_words(self):
# """Pull cleaned data from form and join into code_phrase"""
# word_list = []
# word_list.append(self.cleaned_data['word_1'])
# word_list.append(self.cleaned_data['word_2'])
# word_list.append(self.cleaned_data['word_3'])
# word_list.append(self.cleaned_data['word_4'])
# word_list.append(self.cleaned_data['word_5'])
# word_list.append(self.cleaned_data['word_6'])
# join_key = '-'
# code_phrase = join_key.join(word_list)
# return code_phrase
def build_code_phrase(self, cleaned_word_data=[]):
"""Join form cleaned data into code_phrase"""
join_key = '-'
code_phrase = join_key.join(word_list)
code_phrase = join_key.join(cleaned_word_data)
return code_phrase
def sanitize_login_string(self, login_string):
"""Santizes the login string"""
#convert to lowercase and strip whitespace
working = login_string.lower().strip()
working = working.replace('_', '-').replace(',', '-').replace(' ', '-')
if '--' in working:
while '--' in working:
working = working.replace('--', '-')
return working
def clean(self):
"""Custom clean method for form"""
# Call the parent method
cleaned_data = super().clean()
# Set two flags for strings and all words filled.
string_filled = False
all_words_filled = True
any_words = False
# Grab the login string field
if cleaned_data.get('login_string') != '':
login_string = cleaned_data.get('login_string')
string_filled = True
# Now get the individual word login fields - first set up a list called word_counter
word_counter = ['word_1', 'word_2', 'word_3', 'word_4', 'word_5', 'word_6']
# iterate through the wordlist, matching to cleaned_data and further santizing the input by
# calling .lower() on each word before passing it to a cleaned_word_data dictionary to be joined.
cleaned_word_data = []
for word in word_counter:
value = cleaned_data.get(word)
cleaned_word_data.append(value.lower())
# if any of the words are left blank, flip the flag for all_words_filled.
if value == '':
all_words_filled = False
# if any word is filled in, flip the flap for any_words.
if value != '':
any_words = True
if string_filled == True:
if any_words == True:
raise ValidationError(
"""ERROR: It looks like you've filled out both the login words and the login string/phrase fields.
Please choose one or the other.""")
else:
user_identifier = self.sanitize_login_string(login_string)
self.cleaned_data['user_identifier'] = user_identifier
if string_filled == False:
if all_words_filled == False:
if any_words == True:
raise ValidationError(
"""Make sure to fill out either all words of your user identifier above, or paste the string
version of your user identifier below.""")
else:
self.cleaned_data['blank'] = True
else:
user_identifier = self.build_code_phrase(cleaned_word_data=cleaned_word_data)
self.cleaned_data['user_identifier'] = user_identifier
# Forms relating to User Objects:
class Anonymous_Ticket_Base_Search_Form(forms.Form):
......
......@@ -17,21 +17,90 @@
{% endblock %}
<!-- block content block is where page contents go -->
{% block content %}
<div class="row">
<!-- Explanation block -->
<div class="row mb-4">
<div class="col-12">
<p class="pr-5">
If you've already created a user-identifier code-phrase, you can
enter it below to login. Put one word in each box and do not include
dashes. You can use tab to navigate from one box to the next.
If you've already created a user identifier, you can
enter it below to login, or you can <a href="{% url 'create-identifier' %}">create one now.</a>
</p>
</div>
</div>
<!-- End Explanation block -->
<!-- Start rendering form -->
<form method="get">
<!-- Determine if there are form validation errors. If so, render them here. -->
{% if form.non_field_errors %}
<div class="row mt-2">
<div class="col-12">
{% for error in form.non_field_errors %}
<div class="list-unstyled alert alert-primary" role="alert">
{{error}}
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Render login string block -->
<div class="row">
<div class="col-12">
<form method="get" class="p-5">
<ul class="bg-light p-5 flex-form">{{ form.as_ul }}</ul>
<button type="submit" class="btn btn-lg btn-primary mr-2 mt-3 new-line">Login With This Identifier</button>
</form>
<div class="">
<p>
Enter your user identifier and click the button below.
You can separate the words with commas, spaces, dashes, or underscores.
</p>
</div>
<div class="bg-light p-4 flex-form">
{{ form.login_string}}
</div>
</div>
</div>
<!-- Render words fields
<div class="row mt-2">
<div class="col-12">
<div class="mt-4">
<p class="">
Option 2: Enter the words of your user identifier below
to login. Put one word in each box. You can use tab to navigate from one box to the next.
</p>
</div>
<ul class="bg-light p-4 flex-form">
<li>
{{ form.word_1.errors}}
{{ form.word_1 }}
{{ form.word_1.label_tag}}
</li>
<li>
{{ form.word_2.errors}}
{{ form.word_2 }}
{{ form.word_2.label_tag}}
</li>
<li>
{{ form.word_3.errors}}
{{ form.word_3 }}
{{ form.word_3.label_tag}}
</li>
<li>
{{ form.word_4.errors}}
{{ form.word_4 }}
{{ form.word_4.label_tag}}
</li>
<li>
{{ form.word_5.errors}}
{{ form.word_5 }}
{{ form.word_5.label_tag}}
</li>
<li>
{{ form.word_6.errors}}
{{ form.word_6 }}
{{ form.word_6.label_tag}}
</li>
</ul>
</div> -->
<!-- </div> -->
<!-- Render login button block -->
<div>
<button type="submit" class="btn btn-lg btn-primary mr-2 mt-3 new-line">Login With This Identifier</button>
</div>
</div>
{% endblock %}
\ No newline at end of file
......@@ -22,7 +22,7 @@ from django.core.cache import cache
# (or with coverage) $ coverage run manage.py --tag url.)
# ---------------------CUSTOM TEST FUNCTIONS----------------------------
# URL Tests using Django SimpleTestCase (no need for database.)
# Functions used inside of the testing package during rate-limit tests.
# ----------------------------------------------------------------------
def get_testing_limit_rate(fraction=''):
......@@ -60,6 +60,79 @@ def run_rate_limit_test(self, client, url, form, form_data, follow=False, fracti
tries += 1
return response
# -----DISCRETE FUNCTION UNIT TESTS (SHARED FUNCTIONS, NON-GITLAB)------
# Unit tests for the functions inside of views.py in the top section
# labelled: "Shared Functions - Non Gitlab"
# ----------------------------------------------------------------------
@tag('shared-non-gitlab')
class TestUserIdentifierInDatabase(TestCase):
"""Tests the user_identifier_in_database function."""
def setUp(self):
new_user = UserIdentifier.objects.create(user_identifier =
'duo-atlas-hypnotism-curry-creatable-rubble'
)
self.new_user = new_user
def test_user_identifier_in_database_good_user(self):
"""Tests user_identifier_in_database with known good user."""
user_found = user_identifier_in_database(find_user = self.new_user)
self.assertTrue(user_found)
def test_user_identifier_in_database_bad_user(self):
"""Tests user_identifier_in_database with known bad user."""
user_found = user_identifier_in_database(find_user = 'i-am-a-known-bad-user')
self.assertFalse(user_found)
@tag('shared-non-gitlab')
class TestGetUserAsObject(TestCase):
"""Tests the get_user_as_object function"""
def setUp(self):
new_user = UserIdentifier.objects.create(user_identifier =
'duo-atlas-hypnotism-curry-creatable-rubble'
)
self.new_user = new_user
def test_get_user_as_object(self):
"""Tests user_identifier_in_database with known good user."""
user_to_find = UserIdentifier.objects.get(user_identifier = self.new_user)
self.assertEqual(user_to_find.user_identifier, 'duo-atlas-hypnotism-curry-creatable-rubble')
@tag('shared-non-gitlab')
class TestCheckUser(SimpleTestCase):
"""Test the check_user function."""
def test_check_user_known_good(self):
"""Test the check_user function with known good identifier"""
user_identifier = 'duo-atlas-hypnotism-curry-creatable-rubble'
test_response = check_user(user_identifier = user_identifier)
self.assertTrue(test_response)
def test_check_user_known_good_uppercase(self):
"""Test the check_user function with known good identifier with uppercase"""
user_identifier = 'duo-Atlas-hypnotism-Curry-creatable-Rubble'
test_response = check_user(user_identifier = user_identifier)
self.assertTrue(test_response)
def test_check_user_too_many_words(self):
"""Test the check_user function with an identifier that is too long."""
user_identifier = 'duo-atlas-hypnotism-curry-creatable-rubble-relic-skylight-yield-vocalize-gerbil-finalist'
test_response = check_user(user_identifier = user_identifier)
self.assertFalse(test_response)
def test_check_user_repeated_words(self):
"""Test the check_user function with repeated words"""
user_identifier = 'duo-atlas-hypnotism-curry-creatable-creatable'
test_response = check_user(user_identifier = user_identifier)
self.assertFalse(test_response)
def test_check_user_bad_words(self):
"""Test the check_user function with repeated words"""
user_identifier = 'duo-atlas-hypnotism-curry-creatable-foo'
test_response = check_user(user_identifier = user_identifier)
self.assertFalse(test_response)
# ---------------------------URL TESTS----------------------------------
# URL Tests using Django SimpleTestCase (no need for database.)
# ----------------------------------------------------------------------
......@@ -1260,12 +1333,19 @@ class TestViewsOtherWithoutDatabase(SimpleTestCase):
# for integration with views, etc., is done in views.py above.
# ----------------------------------------------------------------------
@tag('login')
class TestLoginForm(SimpleTestCase):
"""Test the Login Form from forms.py."""
@tag('login-form')
class TestLoginFormIsValid(SimpleTestCase):
"""Test the is_valid() on Login Form from forms.py, including
validation errors raised on custom clean method."""
def test_login_valid_data(self):
"""Test login form with valid data."""
def test_login_valid_blank(self):
"""Test login form with no data."""
form = LoginForm(data = {
})
self.assertTrue(form.is_valid())
def test_login_valid_data_six_words(self):
"""Test login form with six words."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'word_1': 'duo',
......@@ -1277,8 +1357,52 @@ class TestLoginForm(SimpleTestCase):
})
self.assertTrue(form.is_valid())
def test_login_invalid_data(self):
"""Test login form with invalid data."""
def test_login_valid_data_six_words_random_case(self):
"""Test login form with six words regardless of case."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'word_1': 'DUO',
'word_2': 'atlas',
'word_3': 'Hypnotism',
'word_4': 'curry',
'word_5': 'creatable',
'word_6': 'rubble',
})
self.assertTrue(form.is_valid())
def test_login_valid_data_login_string(self):
"""Test login form with a valid string."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'login_string': 'duo-atlas-hypnotism-curry-creatable-rubble' ,
})
self.assertTrue(form.is_valid())
def test_login_invalid_data_five_words(self):
"""Test login form with only five valid words."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'word_1': 'duo',
'word_2': 'atlas',
'word_3': 'hypnotism',
'word_4': 'curry',
'word_5': 'creatable',
})
self.assertFalse(form.is_valid())
self.assertEquals(len(form.errors), 1)
def test_login_invalid_data_any_words_AND_string(self):
"""Test login form with a word and a login string filled out."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'word_1': 'duo',
'login_string': 'duo-atlas-hypnotism-curry-creatable-rubble'
})
self.assertFalse(form.is_valid())
self.assertEquals(len(form.errors), 1)
def test_login_invalid_data_six_words_AND_string(self):
"""Test login form with six valid words AND login string filled out."""
# duo-atlas-hypnotism-curry-creatable-rubble
form = LoginForm(data = {
'word_1': 'duo',
......@@ -1286,10 +1410,87 @@ class TestLoginForm(SimpleTestCase):
'word_3': 'hypnotism',
'word_4': 'curry',
'word_5': 'creatable',
'word_6': 'rubble',
'login_string': 'duo-atlas-hypnotism-curry-creatable-rubble'
})
self.assertFalse(form.is_valid())
self.assertEquals(len(form.errors), 1)
@tag('login-form')
class TestLoginFormAttributes(SimpleTestCase):
"""Test functions of the LoginForm (besides is_valid)"""
def test_build_code_phrase(self):
"""Test login form build_code_phrase method."""
form = LoginForm()
cleaned_word_data = ['word_1', 'word_2', 'word_3']
result = form.build_code_phrase(cleaned_word_data)
self.assertEqual(result, 'word_1-word_2-word_3')
def test_sanitize_login_string_all_dash_no_strip(self):
"""Test sanitize_login_string method with dash
separators and no whitespace to strip."""
form = LoginForm()
test_phrase = 'duo-atlas-hypnotism-curry-creatable-rubble'
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_all_space_no_strip(self):
"""Test sanitize_login_string method with space
separators and no whitespace to strip."""
form = LoginForm()
test_phrase = 'duo atlas hypnotism curry creatable rubble'
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_all_underscore_no_strip(self):
"""Test sanitize_login_string method with underscore
separators and no whitespace to strip."""
form = LoginForm()
test_phrase = 'duo_atlas_hypnotism_curry_creatable_rubble'
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_mixed_separator_no_strip(self):
"""Test sanitize_login_string method with mixed
separators and no whitespace to strip."""
form = LoginForm()
test_phrase = 'duo-atlas hypnotism_curry-creatable rubble'
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_all_dash_left_strip(self):
"""Test sanitize_login_string method with dash
separators and left whitespace to strip."""
form = LoginForm()
test_phrase = ' duo-atlas-hypnotism-curry-creatable-rubble'
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_all_space_right_strip(self):
"""Test sanitize_login_string method with space
separators and right whitespace to strip."""
form = LoginForm()
test_phrase = 'duo atlas hypnotism curry creatable rubble '
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_all_underscore_both_strip(self):
"""Test sanitize_login_string method with underscore
separators and bilateral whitespace to strip."""
form = LoginForm()
test_phrase = ' duo_atlas_hypnotism_curry_creatable_rubble '
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
def test_sanitize_login_string_multiple_mixed_separator_both_strip(self):
"""Test sanitize_login_string method with MULTIPLE mixed
separators and bilateral whitespace to strip."""
form = LoginForm()
test_phrase = ' duo-_-atlas- hypnotism-_-----curry-creatable-,,,,, rubble '
result = form.sanitize_login_string(test_phrase)
self.assertEqual(result, 'duo-atlas-hypnotism-curry-creatable-rubble')
@tag('search_form')
class TestAnonymousTicketProjectSearchForm(TestCase):
"""Test the Anonymous_Ticket_Project_Search_Form"""
......
......@@ -70,6 +70,7 @@ def get_linked_notes(UserIdentifier):
def check_user(user_identifier):
"""Check that a user_identifier meets validation requirements."""
user_identifier = user_identifier.lower()
id_to_test = user_identifier.split('-')
if len(id_to_test) != settings.DICE_ROLLS:
return False
......@@ -95,11 +96,12 @@ def validate_user(view_func):
"""A decorator that calls check_user validator."""
@functools.wraps(view_func)
def validate_user_identifier(request, user_identifier, *args, **kwargs):
get_user_identifier = check_user(user_identifier)
lowercase_user_identifier = user_identifier.lower()
get_user_identifier = check_user(lowercase_user_identifier)
if get_user_identifier == False:
return redirect('user-login-error', user_identifier=user_identifier)
return redirect('user-login-error', user_identifier=lowercase_user_identifier)
else:
response = view_func(request, user_identifier, *args, **kwargs)
response = view_func(request, lowercase_user_identifier, *args, **kwargs)
return response
return validate_user_identifier
......@@ -293,19 +295,22 @@ class CreateIdentifierView(TemplateView):
continue
def login_view(request):
"""Generate a form with fields to allow users to enter their codename. If all
fields are filled out, redirect to appropriate user-landing."""
"""Generate a login form. Note that most processing for this view is in forms.py"""
results = {}
form = LoginForm(request.GET)
# if the LoginForm is filled out, clean data and join the words together
# using the forms join_words function (defined in the form.)
if form.is_valid():
results = form.join_words()
# redirect to user-landing view, passing results dictionary as kwarg
return redirect('user-landing', user_identifier = results)
if 'user_identifier' in form.cleaned_data.keys():
user_identifier = form.cleaned_data['user_identifier']
# redirect to user-landing view, passing results dictionary as kwarg
return redirect('user-landing', user_identifier = user_identifier)
else:
return render (request, 'anonticket/user_login.html', {'form':form, 'results': results})
# if no valid post request, display the form
else:
form = LoginForm
# form = LoginForm
return render (request, 'anonticket/user_login.html', {'form': form})
return render (request, 'anonticket/user_login.html', {'form':form, 'results': results})
@validate_user
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment