Commit 0a707631 authored by Andreas Rimolsrønning's avatar Andreas Rimolsrønning
Browse files

Merge branch 'backend/refactor/feature-envy' into 'master'

Remove feature envy from backend

See merge request !28
parents a7e072ef bbb428a8
Pipeline #128885 passed with stages
in 3 minutes and 24 seconds
......@@ -19,8 +19,13 @@ from django.contrib import admin
from django.urls import include
from django.urls import path
from secfit import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.api_root),
path("", include("comments.urls")),
path("", include("users.urls")),
path("", include("workouts.urls")),
]
......
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(["GET"])
def api_root(request, format=None):
return Response(
{
"users": reverse("user-list", request=request, format=format),
"workouts": reverse("workout-list", request=request, format=format),
"exercises": reverse("exercise-list", request=request, format=format),
"exercise-instances": reverse(
"exercise-instance-list", request=request, format=format
),
"workout-files": reverse(
"workout-file-list", request=request, format=format
),
"comments": reverse("comment-list", request=request, format=format),
"likes": reverse("like-list", request=request, format=format),
}
)
import os
import pathlib
import time
from tests.common import BlackBoxTestServer
from tests.common import login_data
from tests.common import LoginData
here = pathlib.Path(__file__).parent.absolute()
data_dir = here / "data"
backend_port = os.environ.get("BACKEND_PORT", 8000)
frontend_port = os.environ.get("FRONTEND_PORT", 8001)
frontend_address = f"http://localhost:{frontend_port}"
class RememberMeTest(BlackBoxTestServer):
"""Test case for the remember me feature.
When login in with remember me set to true, a remember_me cookie
should be sent to the user.
"""
fixtures = [
data_dir / "users.json",
]
def login_with_remember_me(self, user: LoginData):
"""Log in a user with remember me set to true and wait until redirected
to the workouts page."""
self.get("login.html")
self.assertEqual("Login", self.driver.title)
username_input = self.driver.find_element_by_name("username")
username_input.send_keys(user.username)
password_input = self.driver.find_element_by_name("password")
password_input.send_keys(user.password)
remember_me_checkbox = self.driver.find_element_by_id("rememberMe")
remember_me_checkbox.click()
time.sleep(3)
self.driver.find_element_by_id("btn-login").click()
self.wait.until(lambda driver: driver.title == "Workouts")
def test_user_does_not_have_remember_me_cookie(self):
user = login_data["athlete"]
self.login(user)
cookies = self.driver.get_cookies()
cookie_present = False
for cookie in cookies:
if cookie["name"] == "remember_me":
cookie_present = True
self.assertFalse(cookie_present)
def test_user_has_remember_me_cookie(self):
user = login_data["athlete"]
self.login_with_remember_me(user)
time.sleep(3)
cookies = self.driver.get_cookies()
cookie_present = False
for cookie in cookies:
if cookie["name"] == "remember_me":
cookie_present = True
self.assertTrue(cookie_present)
# Generated by Django 3.1 on 2021-04-14 18:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0009_auto_20210204_1055'),
]
operations = [
migrations.CreateModel(
name='RememberMe',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remember_me', models.CharField(max_length=500)),
],
),
]
......@@ -71,3 +71,17 @@ class Offer(models.Model):
status = models.CharField(max_length=8, choices=STATUS_CHOICES, default=PENDING)
timestamp = models.DateTimeField(auto_now_add=True)
class RememberMe(models.Model):
"""Django model for an remember_me cookie used for remember me
functionality.
Attributes:
remember_me: Value of cookie used for remember me
"""
remember_me = models.CharField(max_length=500)
def __str__(self):
return self.remember_me
......@@ -5,6 +5,7 @@ from rest_framework import serializers
from users.models import AthleteFile
from users.models import Offer
from users.models import RememberMe
class UserSerializer(serializers.HyperlinkedModelSerializer):
......@@ -129,3 +130,18 @@ class OfferSerializer(serializers.HyperlinkedModelSerializer):
"status",
"timestamp",
]
class RememberMeSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for an RememberMe. Hyperlinks are used for relationships by
default.
Serialized fields: remember_me
Attributes:
remember_me: Value of cookie used for remember me functionality
"""
class Meta:
model = RememberMe
fields = ["remember_me"]
from django.urls import include
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.views import TokenRefreshView
from users import views
......@@ -16,4 +19,8 @@ urlpatterns = [
views.AthleteFileDetail.as_view(),
name="athletefile-detail",
),
path("api/auth/", include("rest_framework.urls")),
path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("api/remember_me/", views.RememberMe.as_view(), name="remember_me"),
]
import base64
import pickle
from collections import namedtuple
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.core.signing import Signer
from django.db.models import Q
from rest_framework import generics
from rest_framework import mixins
......@@ -6,6 +12,8 @@ from rest_framework import permissions
from rest_framework.parsers import FormParser
from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken
from users.models import AthleteFile
from users.models import Offer
......@@ -14,6 +22,7 @@ from users.permissions import IsCoach
from users.permissions import IsCurrentUser
from users.serializers import AthleteFileSerializer
from users.serializers import OfferSerializer
from users.serializers import RememberMeSerializer
from users.serializers import UserGetSerializer
from users.serializers import UserPutSerializer
from users.serializers import UserSerializer
......@@ -190,3 +199,48 @@ class AthleteFileDetail(
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
class RememberMe(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
serializer_class = RememberMeSerializer
def get(self, request):
if request.user.is_authenticated == False:
raise PermissionDenied
else:
return Response({"remember_me": self.rememberme()})
def post(self, request):
cookieObject = namedtuple("Cookies", request.COOKIES.keys())(
*request.COOKIES.values()
)
user = self.get_user(cookieObject)
refresh = RefreshToken.for_user(user)
return Response(
{
"refresh": str(refresh),
"access": str(refresh.access_token),
}
)
def get_user(self, cookieObject):
decode = base64.b64decode(cookieObject.remember_me)
user, sign = pickle.loads(decode)
if sign == self.sign_user(user):
return user
def rememberme(self):
creds = [self.request.user, self.sign_user(str(self.request.user))]
return base64.b64encode(pickle.dumps(creds))
def sign_user(self, username):
signer = Signer()
signed_user = signer.sign(username)
return signed_user
# Generated by Django 3.1 on 2021-04-14 18:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('workouts', '0003_rememberme'),
]
operations = [
migrations.DeleteModel(
name='RememberMe',
),
]
......@@ -150,20 +150,6 @@ class WorkoutFile(models.Model):
file = models.FileField(upload_to=workout_directory_path)
class RememberMe(models.Model):
"""Django model for an remember_me cookie used for remember me
functionality.
Attributes:
remember_me: Value of cookie used for remember me
"""
remember_me = models.CharField(max_length=500)
def __str__(self):
return self.remember_me
@dataclass
@functools.total_ordering
class HighScore:
......
......@@ -6,6 +6,7 @@ from rest_framework import parsers
class MultipartJsonParser(parsers.MultiPartParser):
"""Parser for serializing multipart data containing both files and JSON.
Thanks to https://stackoverflow.com/a/50514630
"""
......
......@@ -4,7 +4,6 @@ from rest_framework.serializers import HyperlinkedRelatedField
from workouts.models import Exercise
from workouts.models import ExerciseInstance
from workouts.models import RememberMe
from workouts.models import Workout
from workouts.models import WorkoutFile
......@@ -217,21 +216,6 @@ class ExerciseSerializer(serializers.HyperlinkedModelSerializer):
fields = ["url", "id", "name", "description", "unit", "instances"]
class RememberMeSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for an RememberMe. Hyperlinks are used for relationships by
default.
Serialized fields: remember_me
Attributes:
remember_me: Value of cookie used for remember me functionality
"""
class Meta:
model = RememberMe
fields = ["remember_me"]
class HighScoreSerializer(serializers.Serializer):
"""Serializer for a HighScore.
......
from django.urls import include
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework_simplejwt.views import TokenRefreshView
from workouts import views
urlpatterns = format_suffix_patterns(
[
path("", views.api_root),
path("api/workouts/", views.WorkoutList.as_view(), name="workout-list"),
path(
"api/workouts/<int:pk>/",
......@@ -42,11 +38,5 @@ urlpatterns = format_suffix_patterns(
views.WorkoutFileDetail.as_view(),
name="workoutfile-detail",
),
path("", include("users.urls")),
path("", include("comments.urls")),
path("api/auth/", include("rest_framework.urls")),
path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path("api/remember_me/", views.RememberMe.as_view(), name="remember_me"),
]
)
......@@ -2,23 +2,14 @@
These are mostly class-based views.
"""
import base64
import pickle
from collections import namedtuple
from operator import attrgetter
from django.core.exceptions import PermissionDenied
from django.core.signing import Signer
from django.db.models import Q
from rest_framework import filters
from rest_framework import generics
from rest_framework import mixins
from rest_framework import permissions
from rest_framework.decorators import api_view
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework_simplejwt.tokens import RefreshToken
from workouts.mixins import CreateListModelMixin
from workouts.models import Exercise
......@@ -37,75 +28,10 @@ from workouts.permissions import IsWorkoutPublic
from workouts.serializers import ExerciseInstanceSerializer
from workouts.serializers import ExerciseSerializer
from workouts.serializers import HighScoreSerializer
from workouts.serializers import RememberMeSerializer
from workouts.serializers import WorkoutFileSerializer
from workouts.serializers import WorkoutSerializer
@api_view(["GET"])
def api_root(request, format=None):
return Response(
{
"users": reverse("user-list", request=request, format=format),
"workouts": reverse("workout-list", request=request, format=format),
"exercises": reverse("exercise-list", request=request, format=format),
"exercise-instances": reverse(
"exercise-instance-list", request=request, format=format
),
"workout-files": reverse(
"workout-file-list", request=request, format=format
),
"comments": reverse("comment-list", request=request, format=format),
"likes": reverse("like-list", request=request, format=format),
}
)
class RememberMe(
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView,
):
serializer_class = RememberMeSerializer
def get(self, request):
if request.user.is_authenticated == False:
raise PermissionDenied
else:
return Response({"remember_me": self.rememberme()})
def post(self, request):
cookieObject = namedtuple("Cookies", request.COOKIES.keys())(
*request.COOKIES.values()
)
user = self.get_user(cookieObject)
refresh = RefreshToken.for_user(user)
return Response(
{
"refresh": str(refresh),
"access": str(refresh.access_token),
}
)
def get_user(self, cookieObject):
decode = base64.b64decode(cookieObject.remember_me)
user, sign = pickle.loads(decode)
if sign == self.sign_user(user):
return user
def rememberme(self):
creds = [self.request.user, self.sign_user(str(self.request.user))]
return base64.b64encode(pickle.dumps(creds))
def sign_user(self, username):
signer = Signer()
signed_user = signer.sign(username)
return signed_user
class WorkoutList(
mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView
):
......
Supports Markdown
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