Skip to content

Commit

Permalink
fix: fixing tests
Browse files Browse the repository at this point in the history
  • Loading branch information
KamilPawel committed Jun 22, 2023
1 parent 8d52b65 commit 6e14baf
Show file tree
Hide file tree
Showing 15 changed files with 424 additions and 342 deletions.
42 changes: 21 additions & 21 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cfl_common/common/csp_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"https://api.iconify.design/",
"https://api.simplesvg.com/",
"https://api.unisvg.com/",
"https://api.pwnedpasswords.com",
"https://www.google-analytics.com/",
"https://pyodide-cdn2.iodide.io/v0.15.0/full/",
"https://crowdin.com/",
Expand All @@ -22,6 +23,7 @@
"'self'",
"'unsafe-inline'",
"'unsafe-eval'",
"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js",
"https://cdn.crowdin.com/",
"https://*.onetrust.com/",
"https://code.jquery.com/",
Expand Down
3 changes: 2 additions & 1 deletion cfl_common/common/tests/utils/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from common.helpers.generators import generate_login_id
from common.models import Class, Student
from django.core import mail
from portal.helpers.password import generate_strong_password

from . import email

Expand Down Expand Up @@ -91,7 +92,7 @@ def generate_independent_student_details():
name = "Independent Student %d" % generate_independent_student_details.next_id
email_address = "student%[email protected]" % generate_independent_student_details.next_id
username = email_address
password = "Password2"
password = generate_strong_password()

generate_independent_student_details.next_id += 1

Expand Down
3 changes: 2 additions & 1 deletion cfl_common/common/tests/utils/teacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from common.helpers.emails import generate_token
from common.models import Teacher
from django.core import mail
from portal.helpers.password import generate_strong_password

from . import email

Expand All @@ -13,7 +14,7 @@ def generate_details(**kwargs):
first_name = kwargs.get("first_name", "Test")
last_name = kwargs.get("last_name", f"Teacher {random_int}")
email_address = kwargs.get("email_address", f"testteacher{random_int}@codeforlife.com")
password = kwargs.get("password", "Password2!")
password = kwargs.get("password", generate_strong_password())

return first_name, last_name, email_address, password

Expand Down
3 changes: 2 additions & 1 deletion portal/forms/play.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.utils import timezone

from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
from portal.helpers.password import PasswordStrength, form_clean_password
from portal.helpers.password import PasswordStrength, form_clean_password, check_pwned_password
from portal.helpers.regexes import ACCESS_CODE_PATTERN


Expand Down Expand Up @@ -238,6 +238,7 @@ def clean(self):
password = self.cleaned_data.get("password", None)
confirm_password = self.cleaned_data.get("confirm_password", None)

check_pwned_password(password)
if password and confirm_password and password != confirm_password:
raise forms.ValidationError("Your passwords do not match")

Expand Down
5 changes: 4 additions & 1 deletion portal/forms/teach.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV2Invisible
from common.helpers.emails import send_verification_email
from portal.helpers.password import check_pwned_password
from common.models import Student, stripStudentName, UserSession, Teacher
from django import forms
from django.contrib.auth import authenticate
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from game.models import Episode


from portal.forms.error_messages import INVALID_LOGIN_MESSAGE
from portal.helpers.password import PasswordStrength, form_clean_password
from portal.helpers.ratelimit import clear_ratelimit_cache_for_user
Expand Down Expand Up @@ -39,7 +41,7 @@ def clean(self):

password = self.cleaned_data.get("teacher_password", None)
confirm_password = self.cleaned_data.get("teacher_confirm_password", None)

check_pwned_password(password)
check_passwords(password, confirm_password)

return self.cleaned_data
Expand Down Expand Up @@ -114,6 +116,7 @@ def clean(self):
return self.cleaned_data

def check_password_errors(self, password, confirm_password, current_password):
check_pwned_password(password)
check_passwords(password, confirm_password)

if not self.user.check_password(current_password):
Expand Down
47 changes: 47 additions & 0 deletions portal/helpers/password.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import re
import hashlib
import requests
import secrets
import string
from enum import Enum, auto

from django import forms
Expand All @@ -7,6 +11,27 @@
from django.core.exceptions import ValidationError


def generate_strong_password(password_length=12):
if password_length < 4:
print("Password length should be at least 4.")
return None

# The password will contain at least one lowercase, one uppercase, one digit, and one special character.
all_possible_characters = string.ascii_letters + string.digits + string.punctuation

while True:
generated_password = "".join(secrets.choice(all_possible_characters) for i in range(password_length))
if (
any(character.islower() for character in generated_password)
and any(character.isupper() for character in generated_password)
and any(character.isdigit() for character in generated_password)
and any(character in string.punctuation for character in generated_password)
):
break

return generated_password


class PasswordStrength(Enum):
STUDENT = auto()
INDEPENDENT = auto()
Expand Down Expand Up @@ -106,3 +131,25 @@ def check_update_password(form, user, request, data):
update_session_auth_hash(request, form.user)

return changing_password


def check_pwned_password(password):
# Create SHA1 hash of the password
sha1_hash = hashlib.sha1(password.encode()).hexdigest()
prefix = sha1_hash[:5] # Take the first 5 characters of the hash as the prefix

# Make a request to the Pwned Passwords API
url = f"https://api.pwnedpasswords.com/range/{prefix}"
response = requests.get(url)

if response.status_code != 200:
return True # backend ignore this and frontend tells the user
# that we cannot verify this at the moment

# Check if the password's hash is found in the response body
hash_suffixes = response.text.split("\r\n")
for suffix in hash_suffixes:
if sha1_hash[5:].upper() == suffix[:35].upper():
raise forms.ValidationError("Your current password is too common")

return True
6 changes: 3 additions & 3 deletions portal/static/portal/js/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function hideScreentimePopup() {
setTimeout(showScreentimePopup, 3600000);
}

let interval;
let timeInterval;

function showSessionPopup() {
$("#session-popup").addClass("popup--fade");
Expand All @@ -95,7 +95,7 @@ function hideSessionPopup() {

function startTimer(duration, minutesDisplay, secondsDisplay) {
let timer = duration, minutes, seconds;
interval = setInterval(function () {
timeInterval = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);

Expand All @@ -112,7 +112,7 @@ function startTimer(duration, minutesDisplay, secondsDisplay) {
}

function resetTimer(minutesDisplay, secondsDisplay) {
clearInterval(interval);
clearInterval(timeInterval);
minutesDisplay.text("2");
secondsDisplay.text("00");
}
Expand Down
Loading

0 comments on commit 6e14baf

Please sign in to comment.