Commit fd00d29d by ayesha-baig Committed by GitHub

Merge pull request #14452 from edx/ayeshabaig/YONK-513

[YONK-513]: Add feature flag which allows for disabling of account cr…
parents 881970a2 61f20679
......@@ -14,9 +14,9 @@ from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from contentstore.models import PushNotificationConfig
from contentstore.tests.test_course_settings import CourseTestCase
from contentstore.tests.utils import parse_json, user, registration, AjaxEnabledTestClient
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from contentstore.tests.test_course_settings import CourseTestCase
from xmodule.modulestore.tests.factories import CourseFactory
import datetime
from pytz import UTC
......@@ -302,6 +302,34 @@ class AuthTestCase(ContentStoreTestCase):
# re-request, and we should get a redirect to login page
self.assertRedirects(resp, settings.LOGIN_REDIRECT_URL + '?next=/home/')
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
def test_signup_button_index_page(self):
"""
Navigate to the home page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
is turned off
"""
response = self.client.get(reverse('homepage'))
self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content)
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
def test_signup_button_login_page(self):
"""
Navigate to the login page and check the Sign Up button is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
is turned off
"""
response = self.client.get(reverse('login'))
self.assertNotIn('<a class="action action-signup" href="/signup">Sign Up</a>', response.content)
@mock.patch.dict(settings.FEATURES, {"ALLOW_PUBLIC_ACCOUNT_CREATION": False})
def test_signup_link_login_page(self):
"""
Navigate to the login page and check the Sign Up link is hidden when ALLOW_PUBLIC_ACCOUNT_CREATION flag
is turned off
"""
response = self.client.get(reverse('login'))
self.assertNotIn('<a href="/signup" class="action action-signin">Don&#39;t have a Studio Account? Sign up!</a>',
response.content)
class ForumTestCase(CourseTestCase):
def setUp(self):
......
......@@ -222,6 +222,9 @@ FEATURES = {
# Set this to False to facilitate cleaning up invalid xml from your modulestore.
'ENABLE_XBLOCK_XML_VALIDATION': True,
# Allow public account creation
'ALLOW_PUBLIC_ACCOUNT_CREATION': True,
}
ENABLE_JASMINE = False
......
<%namespace name='static' file='/static_content.html'/>
<%page expression_filter="h"/>
<%inherit file="base.html" />
<%def name="online_help_token()"><% return "login" %></%def>
......@@ -15,7 +16,9 @@ from openedx.core.djangolib.js_utils import js_escaped_string
<section class="content">
<header>
<h1 class="title title-1">${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}</h1>
<a href="${reverse('signup')}" class="action action-signin">${_("Don't have a {studio_name} Account? Sign up!").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
% if static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<a href="${reverse('signup')}" class="action action-signin">${_("Don't have a {studio_name} Account? Sign up!").format(studio_name=settings.STUDIO_SHORT_NAME)}</a>
% endif
</header>
<article class="content-primary" role="main">
......
......@@ -230,9 +230,11 @@
<li class="nav-item nav-not-signedin-help">
<a href="${get_online_help_info(online_help_token)['doc_url']}" title="${_('Contextual Online Help')}" target="_blank">${_("Help")}</a>
</li>
<li class="nav-item nav-not-signedin-signup">
<a class="action action-signup" href="${reverse('signup')}">${_("Sign Up")}</a>
</li>
% if static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<li class="nav-item nav-not-signedin-signup">
<a class="action action-signup" href="${reverse('signup')}">${_("Sign Up")}</a>
</li>
% endif
<li class="nav-item nav-not-signedin-signin">
<a class="action action-signin" href="${reverse('login')}">${_("Sign In")}</a>
</li>
......
......@@ -9,7 +9,7 @@ from student.models import anonymous_id_for_user, CourseEnrollment, UserProfile
from util.testing import UrlResetMixin
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator
from mock import patch
from mock import patch, Mock
import ddt
import json
......@@ -261,6 +261,14 @@ class AutoAuthEnabledTestCase(AutoAuthTestCase):
return response
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", Mock(return_value=False))
def test_create_account_not_allowed(self):
"""
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
"""
response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
class AutoAuthDisabledTestCase(AutoAuthTestCase):
"""
......
......@@ -4,6 +4,7 @@ import json
import unittest
import ddt
from mock import patch
from django.conf import settings
from django.contrib.auth.models import User, AnonymousUser
from django.core.urlresolvers import reverse
......@@ -404,6 +405,14 @@ class TestCreateAccount(TestCase):
UserAttribute.get_user_attribute(user, REGISTRATION_UTM_CREATED_AT)
)
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", mock.Mock(return_value=False))
def test_create_account_not_allowed(self):
"""
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
"""
response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
@ddt.ddt
class TestCreateAccountValidation(TestCase):
......
......@@ -23,6 +23,7 @@ from django.contrib.auth.views import password_reset_confirm
from django.contrib import messages
from django.core.context_processors import csrf
from django.core import mail
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, NoReverseMatch, reverse_lazy
from django.core.validators import validate_email, ValidationError
from django.db import IntegrityError, transaction
......@@ -1549,6 +1550,13 @@ def _do_create_account(form, custom_form=None):
Note: this function is also used for creating test users.
"""
# Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
if not configuration_helpers.get_value(
'ALLOW_PUBLIC_ACCOUNT_CREATION',
settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
):
raise PermissionDenied()
errors = {}
errors.update(form.errors)
if custom_form:
......@@ -1970,6 +1978,13 @@ def create_account(request, post_override=None):
JSON call to create new edX account.
Used by form in signup_modal.html, which is included into navigation.html
"""
# Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
if not configuration_helpers.get_value(
'ALLOW_PUBLIC_ACCOUNT_CREATION',
settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
):
return HttpResponseForbidden(_("Account creation not allowed."))
warnings.warn("Please use RegistrationView instead.", DeprecationWarning)
try:
......@@ -2074,6 +2089,8 @@ def auto_auth(request):
user.save()
profile = UserProfile.objects.get(user=user)
reg = Registration.objects.get(user=user)
except PermissionDenied:
return HttpResponseForbidden(_("Account creation not allowed."))
# Set the user's global staff bit
if is_staff is not None:
......
......@@ -40,6 +40,7 @@ from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account
from openedx.core.djangoapps.user_api.accounts import EMAIL_MAX_LENGTH
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from student.tests.factories import UserFactory
from student_account.views import account_settings_context, get_user_orders
......@@ -735,3 +736,30 @@ class MicrositeLogistrationTests(TestCase):
self.assertEqual(resp.status_code, 200)
self.assertNotIn('<div id="login-and-registration-container"', resp.content)
class AccountCreationTestCaseWithSiteOverrides(SiteMixin, TestCase):
"""
Test cases for Feature flag ALLOW_PUBLIC_ACCOUNT_CREATION which when
turned off disables the account creation options in lms
"""
def setUp(self):
"""Set up the tests"""
super(AccountCreationTestCaseWithSiteOverrides, self).setUp()
# Set the feature flag ALLOW_PUBLIC_ACCOUNT_CREATION to False
self.site_configuration_values = {
'ALLOW_PUBLIC_ACCOUNT_CREATION': False
}
self.site_domain = 'testserver1.com'
self.set_up_site(self.site_domain, self.site_configuration_values)
def test_register_option_login_page(self):
"""
Navigate to the login page and check the Register option is hidden when
ALLOW_PUBLIC_ACCOUNT_CREATION flag is turned off
"""
response = self.client.get(reverse('signin_user'))
self.assertNotIn('<a class="btn-neutral" href="/register?next=%2Fdashboard">Register</a>',
response.content)
......@@ -124,6 +124,8 @@ def login_and_registration_form(request, initial_mode="login"):
'login_form_desc': json.loads(form_descriptions['login']),
'registration_form_desc': json.loads(form_descriptions['registration']),
'password_reset_form_desc': json.loads(form_descriptions['password_reset']),
'account_creation_allowed': configuration_helpers.get_value(
'ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True))
},
'login_redirect_url': redirect_to, # This gets added to the query string of the "Sign In" button in header
'responsive': True,
......
......@@ -368,6 +368,9 @@ FEATURES = {
# Set this to False to facilitate cleaning up invalid xml from your modulestore.
'ENABLE_XBLOCK_XML_VALIDATION': True,
# Allow public account creation
'ALLOW_PUBLIC_ACCOUNT_CREATION': True,
}
# Ignore static asset files on import which match this pattern
......
......@@ -53,7 +53,7 @@
),
THIRD_PARTY_COMPLETE_URL = '/auth/complete/provider/';
var ajaxSpyAndInitialize = function(that, mode, nextUrl, finishAuthUrl) {
var ajaxSpyAndInitialize = function(that, mode, nextUrl, finishAuthUrl, createAccountOption) {
var options = {
initial_mode: mode,
third_party_auth: {
......@@ -66,7 +66,8 @@
platform_name: 'edX',
login_form_desc: FORM_DESCRIPTION,
registration_form_desc: FORM_DESCRIPTION,
password_reset_form_desc: FORM_DESCRIPTION
password_reset_form_desc: FORM_DESCRIPTION,
account_creation_allowed: createAccountOption
},
$logistrationElement = $('#login-and-registration-container');
......@@ -225,6 +226,20 @@
// Expect that we ignore the external URL and redirect to the dashboard
expect(view.redirect).toHaveBeenCalledWith('/dashboard');
});
it('hides create an account section', function() {
ajaxSpyAndInitialize(this, 'login', '', '', false);
// Expect the Create an account section is hidden
expect((view.$el.find('.toggle-form')).length).toEqual(0);
});
it('shows create an account section', function() {
ajaxSpyAndInitialize(this, 'login', '', '', true);
// Expect the Create an account section is visible
expect((view.$el.find('.toggle-form')).length).toEqual(1);
});
});
});
}).call(this, define || RequireJS.define);
......@@ -69,6 +69,7 @@
this.platformName = options.platform_name;
this.supportURL = options.support_link;
this.createAccountOption = options.account_creation_allowed;
// The login view listens for 'sync' events from the reset model
this.resetModel = new PasswordResetModel({}, {
......@@ -119,7 +120,8 @@
resetModel: this.resetModel,
thirdPartyAuth: this.thirdPartyAuth,
platformName: this.platformName,
supportURL: this.supportURL
supportURL: this.supportURL,
createAccountOption: this.createAccountOption
});
// Listen for 'password-help' event to toggle sub-views
......
......@@ -37,6 +37,7 @@
this.platformName = data.platformName;
this.resetModel = data.resetModel;
this.supportURL = data.supportURL;
this.createAccountOption = data.createAccountOption;
this.listenTo(this.model, 'sync', this.saveSuccess);
this.listenTo(this.resetModel, 'sync', this.resetEmail);
......@@ -53,7 +54,8 @@
currentProvider: this.currentProvider,
providers: this.providers,
hasSecondaryProviders: this.hasSecondaryProviders,
platformName: this.platformName
platformName: this.platformName,
createAccountOption: this.createAccountOption
}
}));
......
......@@ -150,7 +150,7 @@ site_status_msg = get_site_status_msg(course_id)
<li class="item nav-global-04">
<a class="btn-neutral" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
</li>
% else:
% elif static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<li class="item nav-global-04">
<a class="btn-neutral" href="/register${login_query()}">${_("Register")}</a>
</li>
......
......@@ -53,11 +53,13 @@
<% } %>
</form>
<div class="toggle-form">
<div class="section-title">
<h2>
<span class="text"><%- _.sprintf( gettext("New to %(platformName)s?"), context ) %></span>
</h2>
<% if ( context.createAccountOption !== false ) { %>
<div class="toggle-form">
<div class="section-title">
<h2>
<span class="text"><%- _.sprintf( gettext("New to %(platformName)s?"), context ) %></span>
</h2>
</div>
<button class="nav-btn form-toggle" data-type="register"><%- gettext("Create an account") %></button>
</div>
<button class="nav-btn form-toggle" data-type="register"><%- gettext("Create an account") %></button>
</div>
<% } %>
......@@ -8,6 +8,7 @@ from pytz import UTC
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from django.core.validators import validate_email, validate_slug, ValidationError
from django.http import HttpResponseForbidden
from openedx.core.djangoapps.user_api.preferences.api import update_user_preferences
from openedx.core.djangoapps.user_api.errors import PreferenceValidationError
......@@ -292,6 +293,13 @@ def create_account(username, password, email):
AccountPasswordInvalid
UserAPIInternalError: the operation failed due to an unexpected error.
"""
# Check if ALLOW_PUBLIC_ACCOUNT_CREATION flag turned off to restrict user account creation
if not configuration_helpers.get_value(
'ALLOW_PUBLIC_ACCOUNT_CREATION',
settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION', True)
):
return HttpResponseForbidden(_("Account creation not allowed."))
# Validate the username, password, and email
# This will raise an exception if any of these are not in a valid format.
_validate_username(username)
......
......@@ -442,3 +442,11 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
return False
else:
return True
@patch("openedx.core.djangoapps.site_configuration.helpers.get_value", Mock(return_value=False))
def test_create_account_not_allowed(self):
"""
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
"""
response = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
self.assertEqual(response.status_code, 403)
......@@ -19,6 +19,7 @@ from pytz import common_timezones_set, UTC
from social.apps.django_app.default.models import UserSocialAuth
from django_comment_common import models
from openedx.core.djangoapps.site_configuration.helpers import get_value
from openedx.core.lib.api.test_utils import ApiTestCase, TEST_API_KEY
from openedx.core.lib.time_zone_utils import get_display_time_zone
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase, skip_unless_lms
......@@ -1774,6 +1775,24 @@ class RegistrationViewTest(ThirdPartyAuthTestMixin, UserAPITestCase):
self.assertContains(response, 'Kosovo')
def test_create_account_not_allowed(self):
"""
Test case to check user creation is forbidden when ALLOW_PUBLIC_ACCOUNT_CREATION feature flag is turned off
"""
def _side_effect_for_get_value(value, default=None):
"""
returns a side_effect with given return value for a given value
"""
if value == 'ALLOW_PUBLIC_ACCOUNT_CREATION':
return False
else:
return get_value(value, default)
with mock.patch('openedx.core.djangoapps.site_configuration.helpers.get_value') as mock_get_value:
mock_get_value.side_effect = _side_effect_for_get_value
response = self.client.post(self.url, {"email": self.EMAIL, "username": self.USERNAME})
self.assertEqual(response.status_code, 403)
@httpretty.activate
@ddt.ddt
......
......@@ -4,9 +4,9 @@ import copy
from opaque_keys import InvalidKeyError
from django.conf import settings
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.http import HttpResponse, HttpResponseForbidden
from django.core.urlresolvers import reverse
from django.core.exceptions import ImproperlyConfigured, NON_FIELD_ERRORS, ValidationError
from django.core.exceptions import ImproperlyConfigured, NON_FIELD_ERRORS, ValidationError, PermissionDenied
from django.utils.translation import ugettext as _
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect, csrf_exempt
......@@ -302,6 +302,7 @@ class RegistrationView(APIView):
HttpResponse: 400 if the request is not valid.
HttpResponse: 409 if an account with the given username or email
address already exists
HttpResponse: 403 operation not allowed
"""
data = request.POST.copy()
......@@ -352,6 +353,8 @@ class RegistrationView(APIView):
for field, error_list in err.message_dict.items()
}
return JsonResponse(errors, status=400)
except PermissionDenied:
return HttpResponseForbidden(_("Account creation not allowed."))
response = JsonResponse({"success": True})
set_logged_in_cookies(request, response, user)
......
......@@ -135,7 +135,7 @@ site_status_msg = get_site_status_msg(course_id)
<div class="item nav-courseware-02">
<a class="btn-neutral btn-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
</div>
% else:
% elif static.get_value('ALLOW_PUBLIC_ACCOUNT_CREATION', settings.FEATURES.get('ALLOW_PUBLIC_ACCOUNT_CREATION')):
<div class="item nav-courseware-02">
<a class="btn-neutral btn-register" href="/register">${_("Register")}</a>
</div>
......
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