Commit 43c7d408 authored by Birin Sanchez's avatar Birin Sanchez
Browse files

* Do not allow to delete an account if there are pending orders.



* When deleting an account do not remove WC account but disable it in
  practice by resetting password and setting contact email to LDH
  provided email.

* Also remove all PII in WC account, orders and subscriptions.

* delete_user CLI command also checks for pending orders before
  deleting an account.
Signed-off-by: Birin Sanchez's avatarBirin Sanchez <birin.sanchez@puri.sm>
parent 2f9525dd
......@@ -20,6 +20,13 @@ class SubscriptionParseError(Exception):
pass
class OrderParseError(Exception):
"""Raised when the order being parsed contains not expected data.
"""
pass
def get_username_from_woo_customer_id(customer_id, woo=None):
if woo is None:
woo = get_woo_connection()
......@@ -246,11 +253,27 @@ def forced_update(user):
parsed_sub['label'], user.username, parsed_sub['status']))
def parse_order(order):
if order is None:
raise OrderParseError('Cannot parse None order.')
pending_status = ['pending', 'on-hold']
status = order.get('status', None)
parsed_order = {}
if status:
parsed_order['status'] = status
parsed_order['is_pending'] = status in pending_status
else:
raise OrderParseError('Error: order has no status.')
return parsed_order
def delete_account(user, purge_n=0, purge=False):
"""When called with purge=False it will remove user from all LDAP
groups, terminate tunnel account, delete WooCommerce account, mark
the user as inactive, set a gibberish LDAP password and set purge
value to purge_n.
groups, terminate tunnel account, remove PII from WooCommerce
account, mark the user as inactive, set a gibberish password for
LDAP and WooCommerce and set purge value to purge_n.
When called with purge=True it will fully delete the user from
LDAP and from Django DB.
......@@ -272,12 +295,16 @@ def delete_account(user, purge_n=0, purge=False):
except TunnelManager.TerminateAccountError as e:
logger.error(repr(e))
# Delete WooCommerce account
user.delete_woocommerce_account()
# Set unusable password for WooCommerce
user.set_woocommerce_password(make_password(None))
# Set unusable password for LDAP
user.set_ldap_password(make_password(None))
# Clear billing and recovery emails
user.email = ''
user.billing_email = ''
# Mark user as deleted in Django
user.is_active = False
......@@ -285,9 +312,21 @@ def delete_account(user, purge_n=0, purge=False):
user.purge = purge_n
user.save()
# Cancell all user subscriptions
subs = get_user_subscriptions(user)
if subs:
for sub in subs:
sub_id = sub.get('id', None)
if sub_id:
set_subscription_status(sub_id, 'cancelled')
# Remove WC PII
user.remove_woocommerce_pii()
if purge:
ldap_user = user.get_ldap()
ldap_user.delete()
user.delete_woocommerce_account()
user.delete()
......
from django.core.management.base import BaseCommand
from django.core.validators import EmailValidator, ValidationError
from django.conf import settings
from limitmonitor.common import delete_account
from limitmonitor.common import delete_account, parse_order, OrderParseError
from purist.models import User
class Command(BaseCommand):
help = """Removes user from all LDAP groups, terminates tunnel account,
deletes WooCommerce account, marks the user as inactive, sets a
gibberish LDAP password and sets purge value to default value 0."""
deactivates WooCommerce account, marks the user as inactive, sets
a gibberish LDAP password and sets purge value to default value
0."""
def add_arguments(self, parser):
parser.add_argument('email', type=str,
......@@ -23,8 +24,8 @@ class Command(BaseCommand):
parser.add_argument('--full',
action='store_true',
help='Also deletes the user from middleware DB \
and LDAP.')
help='Deletes the user from middleware DB, LDAP \
and WooCommerce.')
def handle(self, *args, **options):
email = options['email']
......@@ -50,6 +51,20 @@ class Command(BaseCommand):
self.stdout.write(msg)
return
orders = user.get_woocommerce_orders()
if orders is not None:
for order in orders:
try:
parsed_o = parse_order(order)
except OrderParseError:
msg = 'Error parsing orders for user: {}'.format(email)
self.stdout.write(msg)
return
if parsed_o['is_pending']:
msg = 'User {} has pending orders.'.format(email)
self.stdout.write(msg)
return
msg = 'Deleting user {}...'.format(email)
self.stdout.write(msg)
delete_account(user, purge_n=purge_n, purge=full)
......
......@@ -440,6 +440,86 @@ class User(AbstractUser):
result.append(l.service)
return result
def get_woocommerce_orders(self):
user_wc_id = self.get_woocommerce_id()
if user_wc_id is None:
# The user does not have WC account
return None
woo = get_woo_connection()
orders = woo.get(
'orders?customer={}'.format(user_wc_id)).json()
if len(orders) >= 1:
return orders
else:
return None
def remove_woocommerce_pii(self):
"""Removes all personally identifying information stored in WC.
"""
user_wc_id = self.get_woocommerce_id()
if user_wc_id is None:
# The user does not have WC account
return None
# remove user data
clean_shipping_data = {
"first_name": "",
"last_name": "",
"company": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"postcode": "",
"country": ""
}
clean_billing_data = {
"first_name": "",
"last_name": "",
"company": "",
"address_1": "",
"address_2": "",
"city": "",
"state": "",
"postcode": "",
"country": "",
"email": self.get_identity(),
"phone": ""
}
user_data = {
"email": self.get_identity(),
"first_name": "",
"last_name": "",
"username": self.get_identity(),
"billing": clean_billing_data,
"shipping": clean_shipping_data,
}
query = "customers/{}".format(user_wc_id)
self.woo_put_json(query, user_data)
# Remove orders PII
clean_data = {
"billing": clean_billing_data,
"shipping": clean_shipping_data,
}
orders = self.get_woocommerce_orders()
if orders:
for order in orders:
order_id = order.get('id', None)
if order_id:
query_o = "orders/{}".format(order_id)
self.woo_post_json(query_o, clean_data)
# Remove subscription PII
subs = self.woo_get_json('subscriptions?customer={}'.format(
user_wc_id))
if len(subs) >= 1:
for sub in subs:
sub_id = sub.get('id', None)
if sub_id:
query_s = "subscriptions/{}".format(sub_id)
self.woo_post_json(query_s, clean_data)
def encrypt(data):
# Encrypt data with Fernet
......
......@@ -5,8 +5,15 @@
{% block title %}{% trans 'Delete Account' %}{% endblock %}
{% block extra_css%}{% include "purist/spinner.html" %}{% endblock %}
{% block header %}{% trans 'Delete Account' %}{% endblock %}
{% block byline %}{% trans 'Are you sure you want to delete your account?' %}{% endblock %}
{% block byline %}
{% if can_delete %}
{% trans 'Are you sure you want to delete your account?' %}
{% else %}
{% trans 'You cannot currently delete your account' %}
{% endif %}
{% endblock %}
{% block content %}
{% if can_delete %}
<div style="padding: 20px;">
<p><strong>WARNING!</strong></p>
Deleting your account will permanently delete all your data, and your username will no longer be available.
......@@ -20,6 +27,12 @@
<div class="spinner"></div>
<p class="wait">Please wait ...</p>
</div>
{% else %}
{% trans 'You have' %} <a href="{{ shoplink }}"> {% trans 'pending orders that are incomplete' %} </a>{% trans '. Once they are completed you can can cancel your account.' %}
{% if paid %}
<p>{% trans '[To stop subscription payments in the meantime, please downgrade to a Basic account.]' %}
{% endif %}
{% endif %}
{% endblock %}
{% block extra_js %}
<script>
......
......@@ -18,7 +18,8 @@ from django.contrib.auth.views import PasswordChangeDoneView \
from django.contrib import messages
from django.urls import reverse_lazy
from django.views.generic.edit import UpdateView, FormView
from limitmonitor.common import delete_account
from limitmonitor.common import delete_account, parse_order, OrderParseError
from purist.models import AccountType
class UserDetail(APIView):
......@@ -120,6 +121,30 @@ class DeleteAccountView(LoginRequiredMixin, FormView):
'Your account has been permanently deleted.')
return super(DeleteAccountView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(DeleteAccountView, self).get_context_data(**kwargs)
orders = self.request.user.get_woocommerce_orders()
context['shoplink'] = settings.WOO_URL
context['paid'] = (
self.request.user.account_type == AccountType.COMPLETE
or self.request.user.account_type == AccountType.GROUP
)
if orders is None:
context['can_delete'] = True
else:
for order in orders:
try:
parsed_o = parse_order(order)
except OrderParseError:
context['can_delete'] = False
break
if parsed_o['is_pending']:
context['can_delete'] = False
break
else:
context['can_delete'] = True
return context
def home(request):
render_data = {
......
Markdown is supported
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