Skip to content
Snippets Groups Projects
Unverified Commit 90c9103a authored by Zachary Hancock's avatar Zachary Hancock Committed by GitHub
Browse files

command to remove social auth users (#21280)

remove social auth users command
parent 1f028e0b
No related merge requests found
"""
Management command to remove social auth users. Intended for use in masters
integration sandboxes to allow partners reset users and enrollment data.
"""
from __future__ import absolute_import
import logging
from django.conf import settings
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from six.moves import input
from third_party_auth.models import SAMLProviderConfig
log = logging.getLogger(__name__)
class Command(BaseCommand):
"""
Management command to remove all social auth entries AND the corresponding edX
users for a given IDP.
Usage:
manage.py remove_social_auth_users gtx
"""
confirmation_prompt = "Type 'confirm' to continue with deletion\n"
def add_arguments(self, parser):
parser.add_argument('IDP', help='slug for the idp to remove all users from')
parser.add_argument(
'--force',
action='store_true',
help='Skip manual confirmation step before deleting objects',
)
@transaction.atomic
def handle(self, *args, **options):
slug = options['IDP']
if not settings.FEATURES.get('ENABLE_ENROLLMENT_RESET'):
raise CommandError('ENABLE_ENROLLMENT_RESET feature not enabled on this enviroment')
try:
SAMLProviderConfig.objects.current_set().get(slug=slug)
except SAMLProviderConfig.DoesNotExist:
raise CommandError(u'No SAML provider found for slug {}'.format(slug))
users = User.objects.filter(social_auth__provider=slug)
user_count = len(users)
count, models = users.delete()
log.info(
u'\n%s users and their related models will be deleted:\n%s\n',
user_count,
models,
)
if not options['force']:
confirmation = input(self.confirmation_prompt)
if confirmation != 'confirm':
raise CommandError('User confirmation required. No records have been modified')
log.info(u'Deleting %s records...', count)
"""
Tests for `remove_social_auth_users` management command
"""
from __future__ import absolute_import
import sys
import unittest
from contextlib import contextmanager
from StringIO import StringIO
from uuid import uuid4
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase, override_settings
from social_django.models import UserSocialAuth
from student.models import User
from student.tests.factories import UserFactory
from third_party_auth.management.commands import remove_social_auth_users
from third_party_auth.tests.factories import SAMLProviderConfigFactory
FEATURES_WITH_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_ENABLED['ENABLE_ENROLLMENT_RESET'] = True
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestRemoveSocialAuthUsersCommand(TestCase):
"""
Test django management command
"""
@classmethod
def setUpClass(cls):
super(TestRemoveSocialAuthUsersCommand, cls).setUpClass()
cls.command = remove_social_auth_users.Command()
def setUp(self):
super(TestRemoveSocialAuthUsersCommand, self).setUp()
self.provider_hogwarts = SAMLProviderConfigFactory.create(slug='hogwarts')
self.provider_durmstrang = SAMLProviderConfigFactory.create(slug='durmstrang')
self.user_fleur = UserFactory(username='fleur') # no social auth
self.user_harry = UserFactory(username='harry') # social auth for Hogwarts
self.user_viktor = UserFactory(username='viktor') # social auth for Durmstrang
self.create_social_auth_entry(self.user_harry, self.provider_hogwarts)
self.create_social_auth_entry(self.user_viktor, self.provider_durmstrang)
@contextmanager
def _replace_stdin(self, text):
orig = sys.stdin
sys.stdin = StringIO(text)
yield
sys.stdin = orig
def create_social_auth_entry(self, user, provider):
external_id = uuid4()
UserSocialAuth.objects.create(
user=user,
uid='{0}:{1}'.format(provider.slug, external_id),
provider=provider.slug,
)
def find_user_social_auth_entry(self, username):
UserSocialAuth.objects.get(user__username=username)
@override_settings(FEATURES=FEATURES_WITH_ENABLED)
def test_remove_users(self):
call_command(self.command, self.provider_hogwarts.slug, force=True)
# user with input idp is removed, along with social auth entries
with self.assertRaises(User.DoesNotExist):
User.objects.get(username='harry')
with self.assertRaises(UserSocialAuth.DoesNotExist):
self.find_user_social_auth_entry('harry')
# other users intact
self.user_fleur.refresh_from_db()
self.user_viktor.refresh_from_db()
self.assertIsNotNone(self.user_fleur)
self.assertIsNotNone(self.user_viktor)
# other social auth intact
self.find_user_social_auth_entry(self.user_viktor.username)
@override_settings(FEATURES=FEATURES_WITH_ENABLED)
def test_invalid_idp(self):
invalid_slug = 'jedi-academy'
err_string = u'No SAML provider found for slug {}'.format(invalid_slug)
with self.assertRaisesRegexp(CommandError, err_string):
call_command(self.command, invalid_slug)
@override_settings(FEATURES=FEATURES_WITH_ENABLED)
def test_confirmation_required(self):
""" By default this command will require user input to confirm """
with self._replace_stdin('confirm'):
call_command(self.command, self.provider_hogwarts.slug)
with self.assertRaises(User.DoesNotExist):
User.objects.get(username='harry')
with self.assertRaises(UserSocialAuth.DoesNotExist):
self.find_user_social_auth_entry('harry')
@override_settings(FEATURES=FEATURES_WITH_ENABLED)
def test_confirmation_failure(self):
err_string = 'User confirmation required. No records have been modified'
with self.assertRaisesRegexp(CommandError, err_string):
with self._replace_stdin('no'):
call_command(self.command, self.provider_hogwarts.slug)
# no users should be removed
self.assertEqual(len(User.objects.all()), 3)
self.assertEqual(len(UserSocialAuth.objects.all()), 2)
def test_feature_default_disabled(self):
""" By default this command should not be enabled """
err_string = 'ENABLE_ENROLLMENT_RESET feature not enabled on this enviroment'
with self.assertRaisesRegexp(CommandError, err_string):
call_command(self.command, self.provider_hogwarts.slug, force=True)
......@@ -420,6 +420,9 @@ FEATURES = {
# Whether to display the account deletion section the account settings page
'ENABLE_ACCOUNT_DELETION': True,
# Enable feature to remove enrollments and users. Used to reset state of master's integration environments
'ENABLE_ENROLLMENT_RESET': False,
}
# Settings for the course reviews tool template and identification key, set either to None to disable course reviews
......
......@@ -222,6 +222,9 @@ FEATURES['STORE_BILLING_INFO'] = True
FEATURES['ENABLE_PAID_COURSE_REGISTRATION'] = True
FEATURES['ENABLE_COSMETIC_DISPLAY_PRICE'] = True
######################### Program Enrollments #####################
FEATURES['ENABLE_ENROLLMENT_RESET'] = True
########################## Third Party Auth #######################
if FEATURES.get('ENABLE_THIRD_PARTY_AUTH') and 'third_party_auth.dummy.DummyBackend' not in AUTHENTICATION_BACKENDS:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment