diff --git a/common/djangoapps/student/management/commands/manage_user.py b/common/djangoapps/student/management/commands/manage_user.py index f336fa8de4..6ac38bedee 100644 --- a/common/djangoapps/student/management/commands/manage_user.py +++ b/common/djangoapps/student/management/commands/manage_user.py @@ -12,7 +12,7 @@ from django.core.management.base import BaseCommand, CommandError from django.db import transaction from django.utils.translation import gettext as _ -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password from student.models import UserProfile diff --git a/common/djangoapps/student/management/tests/test_manage_user.py b/common/djangoapps/student/management/tests/test_manage_user.py index e4c6ee05ac..e19974022d 100644 --- a/common/djangoapps/student/management/tests/test_manage_user.py +++ b/common/djangoapps/student/management/tests/test_manage_user.py @@ -11,7 +11,7 @@ from django.contrib.auth.models import Group, User from django.core.management import CommandError, call_command from django.test import TestCase -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password TEST_EMAIL = 'test@example.com' TEST_USERNAME = 'test-user' diff --git a/lms/djangoapps/support/views/manage_user.py b/lms/djangoapps/support/views/manage_user.py index 1d4948b0d1..a56e56d5a6 100644 --- a/lms/djangoapps/support/views/manage_user.py +++ b/lms/djangoapps/support/views/manage_user.py @@ -14,7 +14,7 @@ from rest_framework.generics import GenericAPIView from edxmako.shortcuts import render_to_response from lms.djangoapps.support.decorators import require_support_permission from openedx.core.djangoapps.user_api.accounts.serializers import AccountUserSerializer -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password from util.json_request import JsonResponse diff --git a/openedx/core/djangoapps/user_api/accounts/forms.py b/openedx/core/djangoapps/user_api/accounts/forms.py index ee0b908cf2..cab513e030 100644 --- a/openedx/core/djangoapps/user_api/accounts/forms.py +++ b/openedx/core/djangoapps/user_api/accounts/forms.py @@ -7,7 +7,7 @@ from __future__ import absolute_import from django import forms from django.core.exceptions import ValidationError -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password class RetirementQueueDeletionForm(forms.Form): diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_utils.py b/openedx/core/djangoapps/user_api/accounts/tests/test_utils.py index e9ce9699d7..4f874b4a5c 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_utils.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_utils.py @@ -15,7 +15,7 @@ from student.tests.factories import UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from ..utils import format_social_link, generate_password, validate_social_link +from ..utils import format_social_link, validate_social_link @ddt.ddt @@ -132,29 +132,3 @@ class CompletionUtilsTestCase(SharedModuleStoreTestCase, CompletionWaffleTestMix ) ) self.assertEqual(empty_block_url, None) - - -class GeneratePasswordTest(TestCase): - """Tests formation of randomly generated passwords.""" - - def test_default_args(self): - password = generate_password() - self.assertEqual(12, len(password)) - self.assertTrue(any(c.isdigit for c in password)) - self.assertTrue(any(c.isalpha for c in password)) - - def test_length(self): - length = 25 - self.assertEqual(length, len(generate_password(length=length))) - - def test_chars(self): - char = '!' - password = generate_password(length=12, chars=(char,)) - - self.assertTrue(any(c.isdigit for c in password)) - self.assertTrue(any(c.isalpha for c in password)) - self.assertEqual(char * 10, password[2:]) - - def test_min_length(self): - with self.assertRaises(ValueError): - generate_password(length=7) diff --git a/openedx/core/djangoapps/user_api/accounts/utils.py b/openedx/core/djangoapps/user_api/accounts/utils.py index c281079d6a..c560c970c8 100644 --- a/openedx/core/djangoapps/user_api/accounts/utils.py +++ b/openedx/core/djangoapps/user_api/accounts/utils.py @@ -3,9 +3,7 @@ Utility methods for the account settings. """ from __future__ import absolute_import, unicode_literals -import random import re -import string import waffle from completion import waffle as completion_waffle @@ -184,20 +182,6 @@ def retrieve_last_sitewide_block_completed(user): ) -def generate_password(length=12, chars=string.ascii_letters + string.digits): - """Generate a valid random password""" - if length < 8: - raise ValueError("password must be at least 8 characters") - - choice = random.SystemRandom().choice - - password = '' - password += choice(string.digits) - password += choice(string.ascii_letters) - password += ''.join([choice(chars) for _i in range(length - 2)]) - return password - - def is_secondary_email_feature_enabled(): """ Checks to see if the django-waffle switch for enabling the secondary email feature is active diff --git a/openedx/core/djangoapps/user_api/management/commands/cancel_user_retirement_request.py b/openedx/core/djangoapps/user_api/management/commands/cancel_user_retirement_request.py index fb83464d60..0adc84443b 100644 --- a/openedx/core/djangoapps/user_api/management/commands/cancel_user_retirement_request.py +++ b/openedx/core/djangoapps/user_api/management/commands/cancel_user_retirement_request.py @@ -9,8 +9,8 @@ import logging from django.core.management.base import BaseCommand, CommandError -from openedx.core.djangoapps.user_api.accounts.utils import generate_password from openedx.core.djangoapps.user_api.models import UserRetirementStatus +from openedx.core.djangoapps.user_authn.utils import generate_password LOGGER = logging.getLogger(__name__) diff --git a/openedx/core/djangoapps/user_authn/tests/test_utils.py b/openedx/core/djangoapps/user_authn/tests/test_utils.py index 45b5632447..048c317789 100644 --- a/openedx/core/djangoapps/user_authn/tests/test_utils.py +++ b/openedx/core/djangoapps/user_authn/tests/test_utils.py @@ -11,7 +11,9 @@ from django.test.utils import override_settings from six.moves.urllib.parse import urlencode # pylint: disable=import-error from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory -from openedx.core.djangoapps.user_authn.utils import is_safe_login_or_logout_redirect +from openedx.core.djangoapps.user_authn.utils import ( + generate_password, is_safe_login_or_logout_redirect +) @ddt.ddt @@ -61,3 +63,29 @@ class TestRedirectUtils(TestCase): req = self.request.get('/logout?{}'.format(urlencode(params)), HTTP_HOST=host) actual_is_safe = is_safe_login_or_logout_redirect(req, redirect_url) self.assertEqual(actual_is_safe, expected_is_safe) + + +class GeneratePasswordTest(TestCase): + """Tests formation of randomly generated passwords.""" + + def test_default_args(self): + password = generate_password() + self.assertEqual(12, len(password)) + self.assertTrue(any(c.isdigit for c in password)) + self.assertTrue(any(c.isalpha for c in password)) + + def test_length(self): + length = 25 + self.assertEqual(length, len(generate_password(length=length))) + + def test_chars(self): + char = '!' + password = generate_password(length=12, chars=(char,)) + + self.assertTrue(any(c.isdigit for c in password)) + self.assertTrue(any(c.isalpha for c in password)) + self.assertEqual(char * 10, password[2:]) + + def test_min_length(self): + with self.assertRaises(ValueError): + generate_password(length=7) diff --git a/openedx/core/djangoapps/user_authn/utils.py b/openedx/core/djangoapps/user_authn/utils.py index c33e8c6622..7c47cabba9 100644 --- a/openedx/core/djangoapps/user_authn/utils.py +++ b/openedx/core/djangoapps/user_authn/utils.py @@ -3,6 +3,9 @@ Utility functions used during user authentication. """ from __future__ import absolute_import +import random +import string + from django.conf import settings from django.utils import http from oauth2_provider.models import Application @@ -29,3 +32,17 @@ def is_safe_login_or_logout_redirect(request, redirect_to): redirect_to, allowed_hosts=login_redirect_whitelist, require_https=request.is_secure(), ) return is_safe_url + + +def generate_password(length=12, chars=string.ascii_letters + string.digits): + """Generate a valid random password""" + if length < 8: + raise ValueError("password must be at least 8 characters") + + choice = random.SystemRandom().choice + + password = '' + password += choice(string.digits) + password += choice(string.ascii_letters) + password += ''.join([choice(chars) for _i in range(length - 2)]) + return password diff --git a/openedx/core/djangoapps/user_authn/views/auto_auth.py b/openedx/core/djangoapps/user_authn/views/auto_auth.py index c7ab01dbb1..0ea8e7c7e0 100644 --- a/openedx/core/djangoapps/user_authn/views/auto_auth.py +++ b/openedx/core/djangoapps/user_authn/views/auto_auth.py @@ -18,7 +18,7 @@ from opaque_keys.edx.locator import CourseLocator from lms.djangoapps.verify_student.models import ManualVerification from openedx.core.djangoapps.django_comment_common.models import assign_role -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password from openedx.features.course_experience import course_home_url_name from student.forms import AccountCreationForm from student.helpers import ( diff --git a/openedx/core/djangoapps/user_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index c3feedc520..e52318e544 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -36,7 +36,7 @@ from lms.djangoapps.discussion.notification_prefs.views import enable_notificati from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_api import accounts as accounts_settings -from openedx.core.djangoapps.user_api.accounts.utils import generate_password +from openedx.core.djangoapps.user_authn.utils import generate_password from openedx.core.djangoapps.user_api.preferences import api as preferences_api from openedx.core.djangoapps.user_authn.cookies import set_logged_in_cookies from openedx.core.djangoapps.user_authn.views.registration_form import (