Skip to content

Commit

Permalink
Use one-time token when remove account
Browse files Browse the repository at this point in the history
  • Loading branch information
fowczarek committed Aug 23, 2019
1 parent 33dbcb0 commit cd3fcfd
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 29 deletions.
12 changes: 9 additions & 3 deletions saleor/account/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ def send_account_delete_confirmation_email_with_url(redirect_url, user):
)


def send_account_delete_confirmation_email(user):
"""Trigger sending a account delete email for the given user."""
token = default_token_generator.make_token(user)
_send_account_delete_confirmation_email.delay(user.email, token)


@app.task
def _send_account_delete_confirmation_email_with_url(
recipient_email, redirect_url, token
Expand All @@ -71,14 +77,14 @@ def _send_account_delete_confirmation_email_with_url(


@app.task
def send_account_delete_confirmation_email(recipient_email, token):
def _send_account_delete_confirmation_email(recipient_email, token):
delete_url = build_absolute_uri(
reverse("account:delete-confirm", kwargs={"token": token})
)
_send_account_delete_confirmation_email(recipient_email, delete_url)
_send_delete_confirmation_email(recipient_email, delete_url)


def _send_account_delete_confirmation_email(recipient_email, delete_url):
def _send_delete_confirmation_email(recipient_email, delete_url):
ctx = get_email_base_context()
ctx["delete_url"] = delete_url
send_templated_mail(
Expand Down
9 changes: 5 additions & 4 deletions saleor/account/migrations/0020_user_token.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Generated by Django 2.0.3 on 2018-05-30 12:26
import uuid

from django.db import migrations, models

import saleor.account.models

def get_token():
return str(uuid.uuid4())


class Migration(migrations.Migration):
Expand All @@ -13,8 +16,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name="user",
name="token",
field=models.UUIDField(
default=saleor.account.models.get_token, editable=False
),
field=models.UUIDField(default=get_token, editable=False),
)
]
6 changes: 5 additions & 1 deletion saleor/account/migrations/0021_unique_token.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import uuid

from django.db import migrations, models

from saleor.account.models import get_token

def get_token():
return str(uuid.uuid4())


def create_uuid(apps, schema_editor):
Expand Down
10 changes: 10 additions & 0 deletions saleor/account/migrations/0032_remove_user_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Generated by Django 2.2.4 on 2019-08-22 12:39

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [("account", "0031_auto_20190719_0745")]

operations = [migrations.RemoveField(model_name="user", name="token")]
7 changes: 0 additions & 7 deletions saleor/account/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import uuid

from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser,
Expand Down Expand Up @@ -128,10 +126,6 @@ def staff(self):
return self.get_queryset().filter(is_staff=True)


def get_token():
return str(uuid.uuid4())


class User(PermissionsMixin, ModelWithMetadata, AbstractBaseUser):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=256, blank=True)
Expand All @@ -140,7 +134,6 @@ class User(PermissionsMixin, ModelWithMetadata, AbstractBaseUser):
Address, blank=True, related_name="user_addresses"
)
is_staff = models.BooleanField(default=False)
token = models.UUIDField(default=get_token, editable=False, unique=True)
is_active = models.BooleanField(default=True)
note = models.TextField(null=True, blank=True)
date_joined = models.DateTimeField(default=timezone.now, editable=False)
Expand Down
10 changes: 6 additions & 4 deletions saleor/account/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.contrib import auth, messages
from django.contrib.auth import views as django_views
from django.contrib.auth.decorators import login_required
from django.contrib.auth.tokens import default_token_generator
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
Expand All @@ -22,6 +23,7 @@
get_address_form,
logout_on_password_change,
)
from .models import User


@find_and_assign_anonymous_checkout()
Expand Down Expand Up @@ -156,8 +158,8 @@ def address_delete(request, pk):
@login_required
@require_POST
def account_delete(request):
user = request.user
send_account_delete_confirmation_email.delay(user.email, str(user.token))
user = User.objects.get(pk=request.user.pk)
send_account_delete_confirmation_email(user)
messages.success(
request,
pgettext(
Expand All @@ -170,9 +172,9 @@ def account_delete(request):

@login_required
def account_delete_confirm(request, token):
user = request.user
user = User.objects.get(pk=request.user.pk)

if str(request.user.token) != token:
if not default_token_generator.check_token(user, token):
raise Http404("No such page!")

if request.method == "POST":
Expand Down
1 change: 0 additions & 1 deletion saleor/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3480,7 +3480,6 @@ type User implements Node {
firstName: String!
lastName: String!
isStaff: Boolean!
token: UUID!
isActive: Boolean!
note: String
dateJoined: DateTime!
Expand Down
3 changes: 2 additions & 1 deletion tests/api/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,8 @@ def test_account_delete_staff_user(staff_api_client):
def test_account_delete_other_customer_token(user_api_client):
user = user_api_client.user
other_user = User.objects.create(email="[email protected]")
variables = {"token": other_user.token}
token = default_token_generator.make_token(other_user)
variables = {"token": token}

response = user_api_client.post_graphql(ACCOUNT_DELETE_MUTATION, variables)
content = get_graphql_content(response)
Expand Down
17 changes: 9 additions & 8 deletions tests/test_account.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import re
import uuid
from unittest.mock import patch
from urllib.parse import urlencode

import i18naddress
import pytest
from captcha import constants as recaptcha_constants
from captcha.client import RecaptchaResponse
from django.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError
from django.core.files import File
from django.forms import Form
Expand Down Expand Up @@ -401,25 +401,26 @@ def test_view_account_post_required(customer_user, authorized_client):
assert response.status_code == 405


@patch("saleor.account.views.send_account_delete_confirmation_email.delay")
@patch("saleor.account.views.send_account_delete_confirmation_email")
def test_view_account_delete(
send_confirmation_mock, customer_user, authorized_client, staff_user
):
url = reverse("account:delete")
response = authorized_client.post(url)
assert response.status_code == 302
send_confirmation_mock.assert_called_once_with(
customer_user.email, str(customer_user.token)
)
send_confirmation_mock.assert_called_once_with(customer_user)


def test_view_account_delete_confirm(customer_user, authorized_client):
def test_view_account_delete_confirm(customer_user, staff_user, authorized_client):
# Non existing token
url = reverse("account:delete-confirm", args=[str(uuid.uuid4())])
invalid_token = default_token_generator.make_token(staff_user)
url = reverse("account:delete-confirm", args=[invalid_token])
response = authorized_client.get(url)
assert response.status_code == 404

deletion_url = reverse("account:delete-confirm", args=[customer_user.token])
authorized_client.force_login(customer_user)
token = default_token_generator.make_token(customer_user)
deletion_url = reverse("account:delete-confirm", args=[token])

# getting the page should not delete the user
response = authorized_client.get(deletion_url)
Expand Down

0 comments on commit cd3fcfd

Please sign in to comment.