diff --git a/account/tests/test_api.py b/account/tests/test_api.py index 028b5cf..b063537 100644 --- a/account/tests/test_api.py +++ b/account/tests/test_api.py @@ -32,7 +32,6 @@ def test_token_expiration(api_client_authenticated, users, profiles): @pytest.mark.django_db def test_unauthenticated_cannot_do_anything(api_client, users): - # TODO, add start-poll url after recaptcha integration urls = [ reverse("account:profiles-detail", args=[users.get(username="test1").id]), ] diff --git a/config_dev.env.example b/config_dev.env.example index e3851c7..1a48887 100644 --- a/config_dev.env.example +++ b/config_dev.env.example @@ -53,3 +53,6 @@ STATIC_URL=/static/ # Location of memcached CACHE_LOCATION=127.0.0.1:11211 + +# Must be 16 char long +TOKEN_SECRET= \ No newline at end of file diff --git a/mpbackend/settings.py b/mpbackend/settings.py index bfa5e8e..0addfeb 100644 --- a/mpbackend/settings.py +++ b/mpbackend/settings.py @@ -23,6 +23,7 @@ STATIC_URL=(str, "/static/"), CORS_ORIGIN_WHITELIST=(list, []), CACHE_LOCATION=(str, "127.0.0.1:11211"), + TOKEN_SECRET=(str, None), ) # WARN about env file not being preset. Here we pre-empt it. env_file_path = os.path.join(BASE_DIR, CONFIG_FILE_NAME) @@ -98,7 +99,7 @@ CSRF_TRUSTED_ORIGINS = ["http://localhost:8080"] SECURE_CROSS_ORIGIN_OPENER_POLICY = None - +TOKEN_SECRET = env("TOKEN_SECRET") REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly" diff --git a/profiles/api/utils.py b/profiles/api/utils.py index 690a876..94a47b5 100644 --- a/profiles/api/utils.py +++ b/profiles/api/utils.py @@ -1,9 +1,19 @@ +import base64 + import django_filters +from Crypto.Cipher import AES +from Crypto.Util.Padding import pad from rest_framework.exceptions import ValidationError from profiles.models import PostalCodeResult +def decrypt_text(text, key): + text = pad(text.encode(), 16) + cipher = AES.new(key.encode("utf-8"), AES.MODE_ECB) + return base64.b64encode(cipher.encrypt(text)) + + class CustomValidationError(ValidationError): # The detail field is shown also when DEBUG=False # Ensures the error message is displayed to the user diff --git a/profiles/api/views.py b/profiles/api/views.py index fc83597..1c1f922 100644 --- a/profiles/api/views.py +++ b/profiles/api/views.py @@ -56,7 +56,7 @@ ) from profiles.utils import generate_password, get_user_result -from .utils import PostalCodeResultFilter +from .utils import decrypt_text, PostalCodeResultFilter logger = logging.getLogger(__name__) @@ -187,7 +187,6 @@ def list(self, request, *args, **kwargs): permission_classes=[AllowAny], ) def start_poll(self, request): - # TODO check recaptha uuid4 = uuid.uuid4() username = f"anonymous_{str(uuid4)}" user = User.objects.create(pk=uuid4, username=username, is_generated=True) @@ -196,7 +195,8 @@ def start_poll(self, request): user.profile = Profile.objects.create(user=user) user.save() token, _ = Token.objects.get_or_create(user=user) - response_data = {"token": token.key, "id": user.id} + data = decrypt_text(token.key, settings.TOKEN_SECRET) + response_data = {"data": data, "id": user.id} return Response(response_data, status=status.HTTP_200_OK) @extend_schema( diff --git a/profiles/tests/api/test_postal_code_result.py b/profiles/tests/api/test_postal_code_result.py index 358b378..572ee3c 100644 --- a/profiles/tests/api/test_postal_code_result.py +++ b/profiles/tests/api/test_postal_code_result.py @@ -147,7 +147,6 @@ def test_postal_code_result( ): num_users = 5 num_answers = 0 - start_poll_url = reverse("profiles:question-start-poll") postal_codes = [None, "20100", "20200", "20100", None] q1 = questions_test_result.get(number="1") q1_option_pos = options_test_result.get(question=q1, value=POS) @@ -155,22 +154,19 @@ def test_postal_code_result( # post positive for i in range(num_users): - response = api_client.post(start_poll_url) - token = response.json()["token"] - assert response.status_code == 200 - token = response.json()["token"] - user_id = response.json()["id"] + user = User.objects.create(username=f"user_{i}") + Profile.objects.create(user=user) + token = Token.objects.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) assert User.objects.all().count() == 1 + i - user = User.objects.get(id=user_id) user.profile.postal_code = postal_codes[i] user.profile.optional_postal_code = postal_codes[i] user.profile.save() - api_client.credentials(HTTP_AUTHORIZATION=f"Token {token}") api_client.post(ANSWER_URL, {"option": q1_option_pos.id, "question": q1.id}) num_answers += 1 assert Answer.objects.count() == num_answers - response = api_client.post(reverse("profiles:question-end-poll")) - api_client.credentials() + api_client.post(reverse("profiles:question-end-poll")) + assert PostalCodeResult.objects.count() == 6 assert PostalCode.objects.count() == 3 assert PostalCodeType.objects.count() == 2 @@ -213,13 +209,12 @@ def test_postal_code_result( # post negative, but only to user Home postal code for i in range(num_users): - response = api_client.post(start_poll_url) - token = response.json()["token"] - assert response.status_code == 200 - token = response.json()["token"] - user_id = response.json()["id"] + user = User.objects.create(username=f"neg_user_{i}") + Profile.objects.create(user=user) + token = Token.objects.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + assert User.objects.all().count() == num_users + 1 + i - user = User.objects.get(id=user_id) user.profile.postal_code = postal_codes[i] user.profile.optional_postal_code = None user.profile.save() @@ -227,8 +222,8 @@ def test_postal_code_result( api_client.post(ANSWER_URL, {"option": q1_option_neg.id, "question": q1.id}) num_answers += 1 assert Answer.objects.count() == num_answers - response = api_client.post(reverse("profiles:question-end-poll")) - api_client.credentials() + api_client.post(reverse("profiles:question-end-poll")) + neg_result = results_test_result.get(topic=NEG) pos_result = results_test_result.get(topic=POS) diff --git a/requirements.in b/requirements.in index 146d860..4ef714f 100644 --- a/requirements.in +++ b/requirements.in @@ -17,3 +17,4 @@ django-cors-headers freezegun django-filter pymemcache +pycryptodome \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index abe665e..0f91efd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,9 +17,7 @@ click==8.1.3 # black # pip-tools coverage[toml]==7.2.3 - # via - # coverage - # pytest-cov + # via pytest-cov django==4.2.10 # via # -r requirements.in @@ -88,6 +86,8 @@ psycopg2-binary==2.9.6 # via -r requirements.in pycodestyle==2.10.0 # via flake8 +pycryptodome==3.20.0 + # via -r requirements.in pyflakes==3.0.1 # via flake8 pymemcache==4.0.0 @@ -123,7 +123,6 @@ tomli==2.0.1 # black # build # coverage - # pyproject-hooks # pytest typing-extensions==4.5.0 # via django-modeltranslation