Merge pull request #17561 from edx/jmbowman/PLAT-1976

PLAT-1976 Add waffle switch to block auth_user write attempts
This commit is contained in:
Jeremy Bowman
2018-02-28 17:16:56 -05:00
committed by GitHub
15 changed files with 226 additions and 53 deletions

View File

@@ -11,8 +11,6 @@ from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from django.core.validators import validate_email, 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, AccountValidationError
from six import text_type
from student.models import User, UserProfile, Registration
@@ -20,15 +18,21 @@ from student import forms as student_forms
from student import views as student_views
from util.model_utils import emit_setting_changed_event
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api import errors, accounts, forms, helpers
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
from openedx.core.djangoapps.user_api.errors import (
AccountUpdateError,
AccountValidationError,
PreferenceValidationError,
)
from openedx.core.djangoapps.user_api.preferences.api import update_user_preferences
from openedx.core.lib.api.view_utils import add_serializer_errors
from .serializers import (
AccountLegacyProfileSerializer, AccountUserSerializer,
UserReadOnlySerializer, _visible_fields # pylint: disable=invalid-name
)
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_api import errors, accounts, forms, helpers
# Public access point for this function.
visible_fields = _visible_fields
@@ -242,21 +246,21 @@ def update_account_settings(requesting_user, update, username=None):
except PreferenceValidationError as err:
raise AccountValidationError(err.preference_errors)
except AccountValidationError as err:
except (AccountUpdateError, AccountValidationError) as err:
raise err
except Exception as err:
raise errors.AccountUpdateError(
raise AccountUpdateError(
u"Error thrown when saving account updates: '{}'".format(text_type(err))
)
# And try to send the email change request if necessary.
if changing_email:
if not settings.FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE']:
raise errors.AccountUpdateError(u"Email address changes have been disabled by the site operators.")
raise AccountUpdateError(u"Email address changes have been disabled by the site operators.")
try:
student_views.do_email_change_request(existing_user, new_email)
except ValueError as err:
raise errors.AccountUpdateError(
raise AccountUpdateError(
u"Error thrown from do_email_change_request: '{}'".format(text_type(err)),
user_message=text_type(err)
)
@@ -310,6 +314,9 @@ def create_account(username, password, email):
):
return HttpResponseForbidden(_("Account creation not allowed."))
if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)
# Validate the username, password, and email
# This will raise an exception if any of these are not in a valid format.
_validate_username(username)
@@ -383,6 +390,8 @@ def activate_account(activation_key):
errors.UserAPIInternalError: the operation failed due to an unexpected error.
"""
if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
raise errors.UserAPIInternalError(SYSTEM_MAINTENANCE_MSG)
try:
registration = Registration.objects.get(activation_key=activation_key)
except Registration.DoesNotExist:

View File

@@ -33,6 +33,7 @@ from openedx.core.djangoapps.user_api.accounts.tests.testutils import (
INVALID_USERNAMES,
VALID_USERNAMES_UNICODE
)
from openedx.core.djangoapps.user_api.config.waffle import PREVENT_AUTH_USER_WRITES, SYSTEM_MAINTENANCE_MSG, waffle
from openedx.core.djangoapps.user_api.errors import (
AccountEmailInvalid,
AccountPasswordInvalid,
@@ -41,6 +42,7 @@ from openedx.core.djangoapps.user_api.errors import (
AccountUserAlreadyExists,
AccountUsernameInvalid,
AccountValidationError,
UserAPIInternalError,
UserNotAuthorized,
UserNotFound
)
@@ -408,10 +410,21 @@ class AccountCreationActivationAndPasswordChangeTest(TestCase):
def test_create_account_invalid_username(self, invalid_username):
create_account(invalid_username, self.PASSWORD, self.EMAIL)
def test_create_account_prevent_auth_user_writes(self):
with pytest.raises(UserAPIInternalError, message=SYSTEM_MAINTENANCE_MSG):
with waffle().override(PREVENT_AUTH_USER_WRITES, True):
create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
@raises(UserNotAuthorized)
def test_activate_account_invalid_key(self):
activate_account(u'invalid')
def test_activate_account_prevent_auth_user_writes(self):
activation_key = create_account(self.USERNAME, self.PASSWORD, self.EMAIL)
with pytest.raises(UserAPIInternalError, message=SYSTEM_MAINTENANCE_MSG):
with waffle().override(PREVENT_AUTH_USER_WRITES, True):
activate_account(activation_key)
@skip_unless_lms
def test_request_password_change(self):
# Create and activate an account

View File

@@ -0,0 +1,21 @@
"""
Waffle flags and switches to change user API functionality.
"""
from __future__ import absolute_import
from django.utils.translation import ugettext_lazy as _
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
SYSTEM_MAINTENANCE_MSG = _(u'System maintenance in progress. Please try again later.')
WAFFLE_NAMESPACE = u'user_api'
# Switches
PREVENT_AUTH_USER_WRITES = u'prevent_auth_user_writes'
def waffle():
"""
Returns the namespaced, cached, audited Waffle class for user_api.
"""
return WaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'UserAPI: ')