diff --git a/cloud/webhooks.py b/cloud/webhooks.py index e2b7711..3e5f78d 100644 --- a/cloud/webhooks.py +++ b/cloud/webhooks.py @@ -117,6 +117,10 @@ def insert_or_fetch_user(wh_payload: dict) -> typing.Optional[dict]: my_log.debug('found user %s', db_user['email']) return db_user + if wh_payload.get('date_deletion_requested'): + my_log.info('Received update for a deleted user %s, not creating', bid_str) + return None + # Pretend to create the user, so that we can inspect the resulting # capabilities. This is more future-proof than looking at the list # of roles in the webhook payload. @@ -181,6 +185,10 @@ def user_modified(): my_log.info('Received update for unknown user %r', payload['old_email']) return '', 204 + if payload.get('date_deletion_requested'): + delete_user(db_user, payload) + return '', 204 + # Use direct database updates to change the email and full name. # Also updates the db_user dict so that local_user below will have # the updated information. @@ -219,3 +227,37 @@ def user_modified(): subscription.do_update_subscription(local_user, payload) return '', 204 + + +def delete_user(db_user, payload): + """Handle deletion request coming from BID.""" + my_log = log.getChild('delete_user') + date_deletion_requested = payload['date_deletion_requested'] + bid_str = str(payload['id']) + local_id = db_user['_id'] + my_log.info( + 'User %s with BID=%s requested deletion on %s, soft-deleting the user', + local_id, bid_str, date_deletion_requested, + ) + # Delete all session tokens linked to this user + token_coll = current_app.db('tokens') + delete_res = token_coll.delete_many({'user': local_id}) + my_log.info('Deleted %s session tokens of user %s', delete_res.deleted_count, local_id) + + # Soft-delete the user and clear their PII + users_coll = current_app.db('users') + updates = { + '_deleted': True, + 'email': None, + 'full_name': None, + 'username': None, + 'auth': [], + } + update_res = users_coll.update_one({'_id': local_id}, {'$set': updates}) + if update_res.matched_count != 1: + my_log.error( + 'Soft-deleted %s users %s with BID=%s', + update_res.matched_count, local_id, bid_str, + ) + else: + my_log.warning('Soft-deleted user %s with BID=%s', local_id, bid_str) diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py index 2594480..9619dbc 100644 --- a/tests/test_webhooks.py +++ b/tests/test_webhooks.py @@ -82,6 +82,29 @@ class UserModifiedTest(AbstractWebhookTest): self.assertEqual('ကြယ်ဆွတ်', db_user['full_name']) self.assertEqual(['subscriber'], db_user['roles']) + def test_date_deletion_requested_is_set(self): + payload = {'id': 1112333, + 'old_email': 'old@email.address', + 'full_name': 'ကြယ်ဆွတ်', + 'email': 'new.address+here-there@email.address', + 'roles': ['cloud_subscriber'], + 'date_deletion_requested': '2020-12-31T23:02:03+00:00'} + as_json = json.dumps(payload).encode() + mac = hmac.new(self.hmac_secret, + as_json, hashlib.sha256) + self.post('/api/webhooks/user-modified', + data=as_json, + content_type='application/json', + headers={'X-Webhook-HMAC': mac.hexdigest()}, + expected_status=204) + + # Check the effect on the user + db_user = self.fetch_user_from_db(self.uid) + self.assertIsNone(db_user['email']) + self.assertIsNone(db_user['full_name']) + self.assertIsNone(db_user['username']) + self.assertTrue(db_user['_deleted']) + def test_change_email_unknown_old(self): payload = {'id': 1112333, 'old_email': 'ancient@email.address',