From 8c3bcb90526cbb9013c2d07e4b6bc421a308c537 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 10 Jun 2024 08:10:10 +0000 Subject: [PATCH 1/8] update paths --- codeforlife/settings/django.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codeforlife/settings/django.py b/codeforlife/settings/django.py index 35edba60..a39ba4ab 100644 --- a/codeforlife/settings/django.py +++ b/codeforlife/settings/django.py @@ -144,12 +144,12 @@ def get_databases(base_dir: Path): # pragma: no cover # URLs # https://docs.djangoproject.com/en/3.2/ref/settings/#root-urlconf -ROOT_URLCONF = "src.service.urls" +ROOT_URLCONF = "src.urls" # App # https://docs.djangoproject.com/en/3.2/ref/settings/#wsgi-application -WSGI_APPLICATION = "src.service.wsgi.application" +WSGI_APPLICATION = "main.app" # Password validation # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators From 0599c487a611bcf65e908e40280cd9849d16e70b Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 10 Jun 2024 08:13:11 +0000 Subject: [PATCH 2/8] try entrypoint script --- codeforlife/manage.py | 29 +++++++++++++++++++++++++++++ setup.py | 3 +++ 2 files changed, 32 insertions(+) create mode 100644 codeforlife/manage.py diff --git a/codeforlife/manage.py b/codeforlife/manage.py new file mode 100644 index 00000000..d7214cce --- /dev/null +++ b/codeforlife/manage.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +""" +© Ocado Group +Created on 10/06/2024 at 09:11:09(+01:00). + +Django's command-line utility for administrative tasks. +""" + +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") + try: + # pylint: disable-next=import-outside-toplevel + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index dd65c239..f8628a65 100644 --- a/setup.py +++ b/setup.py @@ -97,4 +97,7 @@ def parse_requirements(packages: t.Dict[str, t.Dict[str, t.Any]]): install_requires=install_requires, extras_require={"dev": dev_requires}, dependency_links=[], + entry_points={ + "console_scripts": ["cflmanage=codeforlife.manage:main"], + }, ) From 408a76cbe9be76949cd961675e563bfcb0211805 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 10 Jun 2024 09:25:51 +0000 Subject: [PATCH 3/8] remove manage script --- codeforlife/manage.py | 29 ----------------------------- setup.py | 3 --- 2 files changed, 32 deletions(-) delete mode 100644 codeforlife/manage.py diff --git a/codeforlife/manage.py b/codeforlife/manage.py deleted file mode 100644 index d7214cce..00000000 --- a/codeforlife/manage.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -""" -© Ocado Group -Created on 10/06/2024 at 09:11:09(+01:00). - -Django's command-line utility for administrative tasks. -""" - -import os -import sys - - -def main(): - """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") - try: - # pylint: disable-next=import-outside-toplevel - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == "__main__": - main() diff --git a/setup.py b/setup.py index f8628a65..dd65c239 100644 --- a/setup.py +++ b/setup.py @@ -97,7 +97,4 @@ def parse_requirements(packages: t.Dict[str, t.Dict[str, t.Any]]): install_requires=install_requires, extras_require={"dev": dev_requires}, dependency_links=[], - entry_points={ - "console_scripts": ["cflmanage=codeforlife.manage:main"], - }, ) From 34d5b8cb6af6e383c1a7d7332c52d4003f2137d9 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Mon, 10 Jun 2024 10:16:21 +0000 Subject: [PATCH 4/8] load fixtures command --- .../user/management/commands/load_fixtures.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 codeforlife/user/management/commands/load_fixtures.py diff --git a/codeforlife/user/management/commands/load_fixtures.py b/codeforlife/user/management/commands/load_fixtures.py new file mode 100644 index 00000000..2346c933 --- /dev/null +++ b/codeforlife/user/management/commands/load_fixtures.py @@ -0,0 +1,38 @@ +""" +© Ocado Group +Created on 10/06/2024 at 10:44:45(+01:00). +""" + +import os +import typing as t + +from django.apps import apps +from django.core.management import call_command +from django.core.management.base import BaseCommand + + +# pylint: disable-next=missing-class-docstring +class Command(BaseCommand): + help = "Loads all the fixtures of the specified apps." + + def add_arguments(self, parser): + parser.add_argument("app_labels", nargs="*", type=str) + + def handle(self, *args, **options): + fixture_labels: t.List[str] = [] + for app_label in {*options["app_labels"], "user"}: + app_config = apps.app_configs[app_label] + fixtures_path = os.path.join(app_config.path, "fixtures") + + self.stdout.write(f"{app_label} fixtures ({fixtures_path}):") + for fixture_label in os.listdir(fixtures_path): + if fixture_label in fixture_labels: + self.stderr.write(f"Duplicate fixture: {fixture_label}") + return + + self.stdout.write(f" - {fixture_label}") + fixture_labels.append(fixture_label) + + self.stdout.write() + + call_command("loaddata", *fixture_labels) From c6776f1c13218f34e0115b132c619177400a1c62 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 12 Jun 2024 13:10:36 +0000 Subject: [PATCH 5/8] fix: backends --- codeforlife/settings/django.py | 6 ++--- codeforlife/user/auth/backends/__init__.py | 9 +++---- .../{email_and_password.py => email.py} | 2 +- ...nd_password_and_class_id.py => student.py} | 2 +- ...ser_id_and_login_id.py => student_auto.py} | 26 +++++++++++-------- 5 files changed, 24 insertions(+), 21 deletions(-) rename codeforlife/user/auth/backends/{email_and_password.py => email.py} (94%) rename codeforlife/user/auth/backends/{first_name_and_password_and_class_id.py => student.py} (94%) rename codeforlife/user/auth/backends/{user_id_and_login_id.py => student_auto.py} (55%) diff --git a/codeforlife/settings/django.py b/codeforlife/settings/django.py index a39ba4ab..e7954b80 100644 --- a/codeforlife/settings/django.py +++ b/codeforlife/settings/django.py @@ -66,11 +66,11 @@ def get_databases(base_dir: Path): # pragma: no cover # https://docs.djangoproject.com/en/3.2/ref/settings/#authentication-backends AUTHENTICATION_BACKENDS = [ - "codeforlife.user.auth.backends.EmailAndPasswordBackend", + "codeforlife.user.auth.backends.EmailBackend", "codeforlife.user.auth.backends.OtpBackend", "codeforlife.user.auth.backends.OtpBypassTokenBackend", - "codeforlife.user.auth.backends.UserIdAndLoginIdBackend", - "codeforlife.user.auth.backends.FirstNameAndPasswordAndClassIdBackend", + "codeforlife.user.auth.backends.StudentBackend", + "codeforlife.user.auth.backends.StudentAutoBackend", ] # Sessions diff --git a/codeforlife/user/auth/backends/__init__.py b/codeforlife/user/auth/backends/__init__.py index 8c7f34ae..8d1e5266 100644 --- a/codeforlife/user/auth/backends/__init__.py +++ b/codeforlife/user/auth/backends/__init__.py @@ -2,11 +2,10 @@ © Ocado Group Created on 01/02/2024 at 14:48:57(+00:00). """ + # TODO: Create a custom auth backend for Django admin permissions -from .email_and_password import EmailAndPasswordBackend -from .first_name_and_password_and_class_id import ( - FirstNameAndPasswordAndClassIdBackend, -) +from .email import EmailBackend from .otp import OtpBackend from .otp_bypass_token import OtpBypassTokenBackend -from .user_id_and_login_id import UserIdAndLoginIdBackend +from .student import StudentBackend +from .student_auto import StudentAutoBackend diff --git a/codeforlife/user/auth/backends/email_and_password.py b/codeforlife/user/auth/backends/email.py similarity index 94% rename from codeforlife/user/auth/backends/email_and_password.py rename to codeforlife/user/auth/backends/email.py index a7cb8650..58f215e7 100644 --- a/codeforlife/user/auth/backends/email_and_password.py +++ b/codeforlife/user/auth/backends/email.py @@ -9,7 +9,7 @@ from .base import BaseBackend -class EmailAndPasswordBackend(BaseBackend): +class EmailBackend(BaseBackend): """Authenticate a user by checking their email and password.""" def authenticate( # type: ignore[override] diff --git a/codeforlife/user/auth/backends/first_name_and_password_and_class_id.py b/codeforlife/user/auth/backends/student.py similarity index 94% rename from codeforlife/user/auth/backends/first_name_and_password_and_class_id.py rename to codeforlife/user/auth/backends/student.py index 387f1f11..aed1cb0b 100644 --- a/codeforlife/user/auth/backends/first_name_and_password_and_class_id.py +++ b/codeforlife/user/auth/backends/student.py @@ -10,7 +10,7 @@ from .base import BaseBackend -class FirstNameAndPasswordAndClassIdBackend(BaseBackend): +class StudentBackend(BaseBackend): """Authenticate a student using their first name, password and class ID.""" user_class = StudentUser diff --git a/codeforlife/user/auth/backends/user_id_and_login_id.py b/codeforlife/user/auth/backends/student_auto.py similarity index 55% rename from codeforlife/user/auth/backends/user_id_and_login_id.py rename to codeforlife/user/auth/backends/student_auto.py index 65651747..1d48e33e 100644 --- a/codeforlife/user/auth/backends/user_id_and_login_id.py +++ b/codeforlife/user/auth/backends/student_auto.py @@ -17,7 +17,7 @@ from .base import BaseBackend -class UserIdAndLoginIdBackend(BaseBackend): +class StudentAutoBackend(BaseBackend): """Authenticate a student using their ID and auto-generated password.""" user_class = StudentUser @@ -25,22 +25,26 @@ class UserIdAndLoginIdBackend(BaseBackend): def authenticate( # type: ignore[override] self, request: t.Optional[HttpRequest], - user_id: t.Optional[int] = None, - login_id: t.Optional[str] = None, + student_id: t.Optional[int] = None, + auto_gen_password: t.Optional[str] = None, **kwargs ): - if user_id is None or login_id is None: + if student_id is None or auto_gen_password is None: return None - user = self.get_user(user_id) - if user: + try: + student = Student.objects.get(id=student_id) + except Student.DoesNotExist: + student = None + + if student: + # TODO: refactor this # Check the url against the student's stored hash. - student = Student.objects.get(new_user=user) if ( - student.login_id - # TODO: refactor this - and get_hashed_login_id(login_id) == student.login_id + student.new_user + and student.login_id + and get_hashed_login_id(auto_gen_password) == student.login_id ): - return user + return student.new_user return None From e3e7d96bfc7505efcc2d87a788d1f3fe289e4bbc Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 12 Jun 2024 14:40:50 +0000 Subject: [PATCH 6/8] allow anyone to get a CSRF cookie --- codeforlife/views/csrf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codeforlife/views/csrf.py b/codeforlife/views/csrf.py index be1ed404..8a149cf5 100644 --- a/codeforlife/views/csrf.py +++ b/codeforlife/views/csrf.py @@ -9,11 +9,14 @@ from rest_framework.response import Response from rest_framework.views import APIView +from ..permissions import AllowAny + class CookieView(APIView): """A view to get a CSRF cookie.""" http_method_names = ["get"] + permission_classes = [AllowAny] @method_decorator(ensure_csrf_cookie) def get(self, request: Request): From 35d8aadaa28a1b09c019a738524eb9c22e4eaff0 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Wed, 12 Jun 2024 14:51:40 +0000 Subject: [PATCH 7/8] rename session cookie --- codeforlife/settings/django.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeforlife/settings/django.py b/codeforlife/settings/django.py index e7954b80..56939e58 100644 --- a/codeforlife/settings/django.py +++ b/codeforlife/settings/django.py @@ -79,7 +79,7 @@ def get_databases(base_dir: Path): # pragma: no cover SESSION_ENGINE = "codeforlife.user.models.session" SESSION_SAVE_EVERY_REQUEST = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True -SESSION_COOKIE_NAME = "sessionid_httponly_true" +SESSION_COOKIE_NAME = "session" SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_AGE = 60 * 60 SESSION_COOKIE_SECURE = True From c4bf17886eb428a0eb7cbd086ad4f8b45f908a10 Mon Sep 17 00:00:00 2001 From: SKairinos Date: Thu, 13 Jun 2024 15:13:18 +0000 Subject: [PATCH 8/8] rename cookie --- codeforlife/settings/django.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codeforlife/settings/django.py b/codeforlife/settings/django.py index 56939e58..68a71c31 100644 --- a/codeforlife/settings/django.py +++ b/codeforlife/settings/django.py @@ -79,7 +79,7 @@ def get_databases(base_dir: Path): # pragma: no cover SESSION_ENGINE = "codeforlife.user.models.session" SESSION_SAVE_EVERY_REQUEST = True SESSION_EXPIRE_AT_BROWSER_CLOSE = True -SESSION_COOKIE_NAME = "session" +SESSION_COOKIE_NAME = "session_key" SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_AGE = 60 * 60 SESSION_COOKIE_SECURE = True