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

Feature/decrypt token #39

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion account/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
]
Expand Down
3 changes: 3 additions & 0 deletions config_dev.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ STATIC_URL=/static/

# Location of memcached
CACHE_LOCATION=127.0.0.1:11211

# Must be 16 char long
TOKEN_SECRET=
3 changes: 2 additions & 1 deletion mpbackend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand Down
10 changes: 10 additions & 0 deletions profiles/api/utils.py
Original file line number Diff line number Diff line change
@@ -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))

Check failure

Code scanning / CodeQL

Use of a broken or weak cryptographic algorithm High

The block mode ECB
is broken or weak, and should not be used.


class CustomValidationError(ValidationError):
# The detail field is shown also when DEBUG=False
# Ensures the error message is displayed to the user
Expand Down
6 changes: 3 additions & 3 deletions profiles/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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)
Expand All @@ -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(
Expand Down
31 changes: 13 additions & 18 deletions profiles/tests/api/test_postal_code_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,30 +147,26 @@ 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)
q1_option_neg = options_test_result.get(question=q1, value=NEG)

# 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
Expand Down Expand Up @@ -213,22 +209,21 @@ 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()
api_client.credentials(HTTP_AUTHORIZATION=f"Token {token}")
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)

Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ django-cors-headers
freezegun
django-filter
pymemcache
pycryptodome
7 changes: 3 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -123,7 +123,6 @@ tomli==2.0.1
# black
# build
# coverage
# pyproject-hooks
# pytest
typing-extensions==4.5.0
# via django-modeltranslation
Expand Down
Loading