Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: client login and models #89

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions codeforlife/tests/model_view_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,13 +611,15 @@ def bulk_destroy(
return response

def _login_user_type(self, user_type: t.Type[AnyUser], **credentials):
# Logout current user (if any) before logging in next user.
self.logout()
assert super().login(
**credentials
), f"Failed to login with credentials: {credentials}."

user = user_type.objects.get(session=self.session.session_key)

if user.session.session_auth_factors.filter(
if user.session.auth_factors.filter(
auth_factor__type=AuthFactor.Type.OTP
).exists():
request = self.request_factory.request()
Expand Down Expand Up @@ -718,20 +720,23 @@ def login_non_school_teacher(self, email: str, password: str = "password"):
)

def login_student(
self, class_id: str, username: str, password: str = "password"
self, class_id: str, first_name: str, password: str = "password"
):
"""Log in a user and assert they are a student.

Args:
class_id: The ID of the class the student belongs to.
username: The user's username.
first_name: The user's first name.
password: The user's password.

Returns:
The student-user.
"""
return self._login_user_type(
StudentUser, username=username, password=password, class_id=class_id
StudentUser,
first_name=first_name,
password=password,
class_id=class_id,
)

def login_indy(self, email: str, password: str = "password"):
Expand Down Expand Up @@ -771,7 +776,7 @@ def login_as(self, user: TypedUser, password: str = "password"):
elif isinstance(user, StudentUser):
auth_user = self.login_student(
user.student.class_field.access_code,
user.username,
user.first_name,
password,
)
elif isinstance(user, IndependentUser):
Expand Down
4 changes: 2 additions & 2 deletions codeforlife/user/auth/backends/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def authenticate( # type: ignore[override]
or request is None
or not isinstance(request.user, User)
or not request.user.userprofile.otp_secret
or not request.user.session.session_auth_factors.filter(
or not request.user.session.auth_factors.filter(
auth_factor__type=AuthFactor.Type.OTP
).exists()
):
Expand All @@ -47,7 +47,7 @@ def authenticate( # type: ignore[override]
user.userprofile.save()

# Delete OTP auth factor from session.
user.session.session_auth_factors.filter(
user.session.auth_factors.filter(
auth_factor__type=AuthFactor.Type.OTP
).delete()

Expand Down
4 changes: 2 additions & 2 deletions codeforlife/user/auth/backends/otp_bypass_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def authenticate( # type: ignore[override]
token is None
or request is None
or not isinstance(request.user, User)
or not request.user.session.session_auth_factors.filter(
or not request.user.session.auth_factors.filter(
auth_factor__type=AuthFactor.Type.OTP
).exists()
):
Expand All @@ -35,7 +35,7 @@ def authenticate( # type: ignore[override]
for otp_bypass_token in request.user.otp_bypass_tokens.all():
if otp_bypass_token.check_token(token):
# Delete OTP auth factor from session.
request.user.session.session_auth_factors.filter(
request.user.session.auth_factors.filter(
auth_factor__type=AuthFactor.Type.OTP
).delete()

Expand Down
7 changes: 3 additions & 4 deletions codeforlife/user/auth/backends/user_id_and_login_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
import typing as t

from common.helpers.generators import get_hashed_login_id
from common.models import Student
from django.contrib.auth.backends import BaseBackend

from ....request import HttpRequest
from ...models import User
from ...models import Student, StudentUser


class UserIdAndLoginIdBackend(BaseBackend):
Expand Down Expand Up @@ -41,6 +40,6 @@ def authenticate( # type: ignore[override]

def get_user(self, user_id: int):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return StudentUser.objects.get(id=user_id)
except StudentUser.DoesNotExist:
return None
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.contrib.auth.backends import BaseBackend

from ....request import HttpRequest
from ...models import User
from ...models import StudentUser


class UsernameAndPasswordAndClassIdBackend(BaseBackend):
Expand All @@ -17,28 +17,28 @@ class UsernameAndPasswordAndClassIdBackend(BaseBackend):
def authenticate( # type: ignore[override]
self,
request: t.Optional[HttpRequest],
username: t.Optional[str] = None,
first_name: t.Optional[str] = None,
password: t.Optional[str] = None,
class_id: t.Optional[str] = None,
**kwargs
):
if username is None or password is None or class_id is None:
if first_name is None or password is None or class_id is None:
return None

try:
user = User.objects.get(
username=username,
user = StudentUser.objects.get(
first_name=first_name,
new_student__class_field__access_code=class_id,
)
if user.check_password(password):
return user
except User.DoesNotExist:
except StudentUser.DoesNotExist:
return None

return None

def get_user(self, user_id: int):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return StudentUser.objects.get(id=user_id)
except StudentUser.DoesNotExist:
return None
161 changes: 158 additions & 3 deletions codeforlife/user/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by Django 3.2.20 on 2024-01-24 18:42
# Generated by Django 3.2.24 on 2024-02-20 15:45

import codeforlife.user.models.user
import django.contrib.auth.models
import django.core.validators
from django.db import migrations, models
Expand All @@ -11,10 +12,44 @@ class Migration(migrations.Migration):
initial = True

dependencies = [
('common', '0048_unique_school_names'),
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='Independent',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('common.student',),
),
migrations.CreateModel(
name='NonSchoolTeacher',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('common.teacher',),
),
migrations.CreateModel(
name='SchoolTeacher',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('common.teacher',),
),
migrations.CreateModel(
name='User',
fields=[
Expand Down Expand Up @@ -62,12 +97,132 @@ class Migration(migrations.Migration):
'unique_together': {('user', 'type')},
},
),
migrations.CreateModel(
name='AdminSchoolTeacher',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.schoolteacher',),
),
migrations.CreateModel(
name='AdminSchoolTeacherUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.AdminSchoolTeacherUserManager()),
],
),
migrations.CreateModel(
name='IndependentUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.IndependentUserManager()),
],
),
migrations.CreateModel(
name='NonAdminSchoolTeacher',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.schoolteacher',),
),
migrations.CreateModel(
name='NonAdminSchoolTeacherUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.NonAdminSchoolTeacherUserManager()),
],
),
migrations.CreateModel(
name='NonSchoolTeacherUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.NonSchoolTeacherUserManager()),
],
),
migrations.CreateModel(
name='SchoolTeacherUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.SchoolTeacherUserManager()),
],
),
migrations.CreateModel(
name='StudentUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.StudentUserManager()),
],
),
migrations.CreateModel(
name='TeacherUser',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('user.user',),
managers=[
('objects', codeforlife.user.models.user.TeacherUserManager()),
],
),
migrations.CreateModel(
name='SessionAuthFactor',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('auth_factor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='session_auth_factors', to='user.authfactor')),
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='session_auth_factors', to='user.session')),
('auth_factor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='user.authfactor')),
('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='auth_factors', to='user.session')),
],
options={
'unique_together': {('session', 'auth_factor')},
Expand Down
Loading
Loading