Skip to content

Commit

Permalink
Merge pull request #4717 from mirumee/4716/redirect_url_in_staff_crea…
Browse files Browse the repository at this point in the history
…te_mutation

Add redirectUrl to staff and user create mutations
  • Loading branch information
maarcingebala committed Sep 9, 2019
2 parents 2058378 + 5ccd99a commit e27a609
Show file tree
Hide file tree
Showing 12 changed files with 330 additions and 80 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Drop deprecated fields from api - #4684 by @fowczarek
- Distinguish OrderLine product name and variant name - #4702 by @fowczarek
- Fix for Digital products - update order status after automatic fulfillment - #4709 by @korycins
- Add redirectUrl to staff and user create mutations - #4717 by @fowczarek

## 2.8.0

Expand Down
12 changes: 5 additions & 7 deletions saleor/account/emails.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from urllib.parse import urlencode
from urllib.parse import urlencode, urlsplit

from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
Expand Down Expand Up @@ -36,11 +36,9 @@ def _send_user_password_reset_email(recipient_email, context, user_id):
@app.task
def _send_password_reset_email_with_url(recipient_email, redirect_url, user_id, token):
params = urlencode({"email": recipient_email, "token": token})
reset_url = "%(redirect_url)s?%(params)s" % {
"redirect_url": redirect_url,
"params": params,
}
_send_password_reset_email(recipient_email, reset_url, user_id)
reset_url = urlsplit(redirect_url)
reset_url = reset_url._replace(query=params)
_send_password_reset_email(recipient_email, reset_url.geturl(), user_id)


def _send_password_reset_email(recipient_email, reset_url, user_id):
Expand Down Expand Up @@ -78,7 +76,7 @@ def _send_account_delete_confirmation_email_with_url(
"redirect_url": redirect_url,
"params": params,
}
_send_account_delete_confirmation_email(recipient_email, delete_url)
_send_delete_confirmation_email(recipient_email, delete_url)


@app.task
Expand Down
16 changes: 8 additions & 8 deletions saleor/core/utils/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@

from django.conf import settings
from django.core.exceptions import ValidationError
from django.http.request import validate_host
from django.http.request import split_domain_port, validate_host


def validate_storefront_url(url):
"""Validate the storefront URL.
Raise ValidationError if URL isn't in RFC 1808 format
or it isn't allowed by ALLOWED_STOREFRONT_HOSTS in settings.
or it isn't allowed by ALLOWED_CLIENT_HOSTS in settings.
"""
try:
parsed_url = urlparse(url)
domain, _ = split_domain_port(parsed_url.netloc)
except ValueError as error:
raise ValidationError({"redirectUrl": str(error)})
if not validate_host(parsed_url.netloc, settings.ALLOWED_STOREFRONT_HOSTS):
raise ValidationError(
{
"redirectUrl": "%s this is not valid storefront address."
% parsed_url.netloc
}
if not validate_host(domain, settings.ALLOWED_CLIENT_HOSTS):
error_message = (
f"{domain or url} is not allowed. Please check "
"`ALLOWED_CLIENT_HOSTS` configuration."
)
raise ValidationError({"redirectUrl": error_message})
4 changes: 2 additions & 2 deletions saleor/dashboard/customer/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ...account import events as account_events
from ...account.models import CustomerNote, User
from ...core.utils import get_paginator_items
from ..emails import send_set_password_customer_email
from ..emails import send_set_password_email
from ..views import staff_member_required
from .filters import UserFilter
from .forms import CustomerDeleteForm, CustomerForm, CustomerNoteForm
Expand Down Expand Up @@ -62,7 +62,7 @@ def customer_create(request):
if form.is_valid():
form.save()
msg = pgettext_lazy("Dashboard message", "Added customer %s") % customer
send_set_password_customer_email.delay(customer.pk)
send_set_password_email(customer)
messages.success(request, msg)
return redirect("dashboard:customer-details", pk=customer.pk)
ctx = {"form": form, "customer": customer}
Expand Down
51 changes: 37 additions & 14 deletions saleor/dashboard/emails.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from urllib.parse import urlencode, urlsplit

from django.conf import settings
from django.contrib.auth.tokens import default_token_generator
from django.urls import reverse
Expand All @@ -11,35 +13,56 @@
from ..core.utils import build_absolute_uri


def _send_set_password_email(pk, template_name):
user = User.objects.get(pk=pk)
uid = urlsafe_base64_encode(force_bytes(user.pk))
def send_set_password_email_with_url(redirect_url, user, staff=False):
"""Trigger sending a set password email for the given customer/staff."""
template_type = "staff" if staff else "customer"
template = f"dashboard/{template_type}/set_password"
token = default_token_generator.make_token(user)
_send_set_user_password_email_with_url.delay(
user.email, redirect_url, token, template
)


def send_set_password_email(user, staff=False):
"""Trigger sending a set password email for the given customer/staff."""
template_type = "staff" if staff else "customer"
template = f"dashboard/{template_type}/set_password"
token = default_token_generator.make_token(user)
_send_set_user_password_email.delay(user.email, user.pk, token, template)


@app.task
def _send_set_user_password_email_with_url(
recipient_email, redirect_url, token, template_name
):
params = urlencode({"email": recipient_email, "token": token})
password_set_url = urlsplit(redirect_url)
password_set_url = password_set_url._replace(query=params)
_send_set_password_email(recipient_email, password_set_url.geturl(), template_name)


@app.task
def _send_set_user_password_email(recipient_email, user_pk, token, template_name):
uid = urlsafe_base64_encode(force_bytes(user_pk))
password_set_url = build_absolute_uri(
reverse(
"account:reset-password-confirm", kwargs={"token": token, "uidb64": uid}
)
)
_send_set_password_email(recipient_email, password_set_url, template_name)


def _send_set_password_email(recipient_email, password_set_url, template_name):
ctx = get_email_base_context()
ctx["password_set_url"] = password_set_url
send_templated_mail(
template_name=template_name,
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[user.email],
recipient_list=[recipient_email],
context=ctx,
)


@app.task
def send_set_password_staff_email(staff_pk):
_send_set_password_email(staff_pk, "dashboard/staff/set_password")


@app.task
def send_set_password_customer_email(pk):
_send_set_password_email(pk, "dashboard/customer/set_password")


@app.task
def send_promote_customer_to_staff_email(staff_pk):
staff = User.objects.get(pk=staff_pk)
Expand Down
4 changes: 2 additions & 2 deletions saleor/dashboard/staff/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ...account.models import User
from ...core.utils import get_paginator_items
from ..emails import send_promote_customer_to_staff_email, send_set_password_staff_email
from ..emails import send_promote_customer_to_staff_email, send_set_password_email
from ..views import staff_member_required
from .filters import StaffFilter
from .forms import StaffForm
Expand Down Expand Up @@ -71,7 +71,7 @@ def staff_create(request):
msg = pgettext_lazy("Dashboard message", "Added staff member %s") % (staff,)
messages.success(request, msg)
if created:
send_set_password_staff_email.delay(staff.pk)
send_set_password_email(user=staff, staff=True)
else:
send_promote_customer_to_staff_email.delay(staff.pk)
return redirect("dashboard:staff-list")
Expand Down
20 changes: 18 additions & 2 deletions saleor/graphql/account/mutations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from ....account import events as account_events, models
from ....account.emails import send_user_password_reset_email_with_url
from ....core.utils.url import validate_storefront_url
from ....dashboard.emails import send_set_password_customer_email
from ....dashboard.emails import send_set_password_email_with_url
from ...account.i18n import I18nMixin
from ...account.types import Address, AddressInput, User
from ...core.mutations import (
Expand Down Expand Up @@ -259,6 +259,12 @@ class UserCreateInput(CustomerInput):
send_password_email = graphene.Boolean(
description="Send an email with a link to set a password"
)
redirect_url = graphene.String(
description=(
"URL of a view where users should be redirected to "
"set the password. URL in RFC 1808 format.",
)
)


class BaseCustomerCreate(ModelMutation, I18nMixin):
Expand Down Expand Up @@ -290,6 +296,14 @@ def clean_input(cls, info, instance, data):
billing_address_data, instance=getattr(instance, BILLING_ADDRESS_FIELD)
)
cleaned_input[BILLING_ADDRESS_FIELD] = billing_address

if cleaned_input.get("send_password_email"):
if not cleaned_input.get("redirect_url"):
raise ValidationError(
{"redirect_url": "Redirect url is required to send a password."}
)
validate_storefront_url(cleaned_input.get("redirect_url"))

return cleaned_input

@classmethod
Expand All @@ -313,7 +327,9 @@ def save(cls, info, instance, cleaned_input):
account_events.customer_account_created_event(user=instance)

if cleaned_input.get("send_password_email"):
send_set_password_customer_email.delay(instance.pk)
send_set_password_email_with_url(
cleaned_input.get("redirect_url"), instance
)


class UserUpdateMeta(UpdateMetaBaseMutation):
Expand Down
20 changes: 18 additions & 2 deletions saleor/graphql/account/mutations/staff.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from ....account.utils import get_random_avatar
from ....checkout import AddressType
from ....core.permissions import get_permissions
from ....dashboard.emails import send_set_password_staff_email
from ....core.utils.url import validate_storefront_url
from ....dashboard.emails import send_set_password_email_with_url
from ....dashboard.staff.utils import remove_staff_member
from ...account.enums import AddressTypeEnum
from ...account.types import Address, AddressInput, User
Expand Down Expand Up @@ -45,6 +46,12 @@ class StaffCreateInput(StaffInput):
send_password_email = graphene.Boolean(
description="Send an email with a link to set the password"
)
redirect_url = graphene.String(
description=(
"URL of a view where users should be redirected to "
"set the password. URL in RFC 1808 format.",
)
)


class CustomerCreate(BaseCustomerCreate):
Expand Down Expand Up @@ -155,6 +162,13 @@ class Meta:
def clean_input(cls, info, instance, data):
cleaned_input = super().clean_input(info, instance, data)

if cleaned_input.get("send_password_email"):
if not cleaned_input.get("redirect_url"):
raise ValidationError(
{"redirect_url": "Redirect url is required to send a password."}
)
validate_storefront_url(cleaned_input.get("redirect_url"))

# set is_staff to True to create a staff user
cleaned_input["is_staff"] = True

Expand All @@ -173,7 +187,9 @@ def save(cls, info, user, cleaned_input):
if create_avatar:
create_user_avatar_thumbnails.delay(user_id=user.pk)
if cleaned_input.get("send_password_email"):
send_set_password_staff_email.delay(user.pk)
send_set_password_email_with_url(
redirect_url=cleaned_input.get("redirect_url"), user=user, staff=True
)


class StaffUpdate(StaffCreate):
Expand Down
2 changes: 2 additions & 0 deletions saleor/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3376,6 +3376,7 @@ input StaffCreateInput {
note: String
permissions: [PermissionEnum]
sendPasswordEmail: Boolean
redirectUrl: String
}

type StaffDelete {
Expand Down Expand Up @@ -3603,6 +3604,7 @@ input UserCreateInput {
isActive: Boolean
note: String
sendPasswordEmail: Boolean
redirectUrl: String
}

type UserUpdateMeta {
Expand Down
4 changes: 2 additions & 2 deletions saleor/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ def get_bool_from_env(name, default_value):
)
MANAGERS = ADMINS

ALLOWED_STOREFRONT_HOSTS = get_list(
os.environ.get("ALLOWED_STOREFRONT_HOSTS", "localhost,127.0.0.1")
ALLOWED_CLIENT_HOSTS = get_list(
os.environ.get("ALLOWED_CLIENT_HOSTS", "localhost,127.0.0.1")
)

INTERNAL_IPS = get_list(os.environ.get("INTERNAL_IPS", "127.0.0.1"))
Expand Down

0 comments on commit e27a609

Please sign in to comment.