Merge pull request #26054 from edx/private_to_public_780deee
Mergeback PR from private to public.
This commit is contained in:
@@ -71,6 +71,7 @@ import six
|
||||
import social_django
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import logout
|
||||
from django.core.mail.message import EmailMessage
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
@@ -674,10 +675,11 @@ def set_logged_in_cookies(backend=None, user=None, strategy=None, auth_entry=Non
|
||||
|
||||
"""
|
||||
if not is_api(auth_entry) and user is not None and user.is_authenticated:
|
||||
request = strategy.request if strategy else None
|
||||
if not user.has_usable_password():
|
||||
msg = "Your account is disabled"
|
||||
logout(request)
|
||||
return JsonResponse(msg, status=403)
|
||||
request = strategy.request if strategy else None
|
||||
# n.b. for new users, user.is_active may be False at this point; set the cookie anyways.
|
||||
if request is not None:
|
||||
# Check that the cookie isn't already set.
|
||||
|
||||
@@ -18,6 +18,8 @@ from openedx.core.djangoapps.user_api.accounts.serializers import AccountUserSer
|
||||
from openedx.core.djangoapps.user_authn.utils import generate_password
|
||||
from common.djangoapps.util.json_request import JsonResponse
|
||||
|
||||
from openedx.core.djangolib.oauth2_retirement_utils import retire_dot_oauth2_models
|
||||
|
||||
|
||||
class ManageUserSupportView(View):
|
||||
"""
|
||||
@@ -73,6 +75,7 @@ class ManageUserDetailView(GenericAPIView):
|
||||
UserPasswordToggleHistory.objects.create(
|
||||
user=user, comment=comment, created_by=request.user, disabled=True
|
||||
)
|
||||
retire_dot_oauth2_models(request.user)
|
||||
else:
|
||||
user.set_password(generate_password(length=25))
|
||||
UserPasswordToggleHistory.objects.create(
|
||||
|
||||
@@ -187,6 +187,17 @@ class AccessTokenExchangeForm(forms.Form):
|
||||
user = backend.do_auth(access_token, allow_inactive_user=True)
|
||||
except (HTTPError, AuthException):
|
||||
pass
|
||||
# check if user is disabled
|
||||
if isinstance(user, User) and not user.has_usable_password():
|
||||
self.request.social_strategy.clean_partial_pipeline(access_token)
|
||||
raise OAuthValidationError(
|
||||
{
|
||||
"error": "account_disabled",
|
||||
"error_description": 'user account is disabled',
|
||||
"error_code": 403
|
||||
}
|
||||
)
|
||||
|
||||
if user and isinstance(user, User):
|
||||
self.cleaned_data["user"] = user
|
||||
else:
|
||||
|
||||
@@ -42,13 +42,16 @@ class AccessTokenExchangeViewTest(AccessTokenExchangeTestMixin):
|
||||
super(AccessTokenExchangeViewTest, self).tearDown()
|
||||
Partial.objects.all().delete()
|
||||
|
||||
def _assert_error(self, data, expected_error, expected_error_description):
|
||||
def _assert_error(self, data, expected_error, expected_error_description, error_code=None):
|
||||
response = self.csrf_client.post(self.url, data)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(response.status_code, error_code if error_code else 400)
|
||||
self.assertEqual(response["Content-Type"], "application/json")
|
||||
expected_data = {u"error": expected_error, u"error_description": expected_error_description}
|
||||
if error_code:
|
||||
expected_data['error_code'] = error_code
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8')),
|
||||
{u"error": expected_error, u"error_description": expected_error_description}
|
||||
expected_data
|
||||
)
|
||||
|
||||
def _assert_success(self, data, expected_scopes):
|
||||
@@ -127,6 +130,15 @@ class AccessTokenExchangeViewTest(AccessTokenExchangeTestMixin):
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_disabled_user(self):
|
||||
"""
|
||||
Test if response status code is correct in case of disabled user.
|
||||
"""
|
||||
self.user.set_unusable_password()
|
||||
self.user.save()
|
||||
self._setup_provider_response(success=True)
|
||||
self._assert_error(self.data, "account_disabled", "user account is disabled", 403)
|
||||
|
||||
|
||||
@unittest.skipUnless(TPA_FEATURE_ENABLED, TPA_FEATURES_KEY + " not enabled")
|
||||
@httpretty.activate
|
||||
|
||||
@@ -28,7 +28,7 @@ class AccessTokenExchangeTestMixin(ThirdPartyOAuthTestMixin):
|
||||
"client_id": self.client_id,
|
||||
}
|
||||
|
||||
def _assert_error(self, _data, _expected_error, _expected_error_description):
|
||||
def _assert_error(self, _data, _expected_error, _expected_error_description, error_code):
|
||||
"""
|
||||
Given request data, execute a test and check that the expected error
|
||||
was returned (along with any other appropriate assertions).
|
||||
|
||||
@@ -114,7 +114,8 @@ class DOTAccessTokenExchangeView(AccessTokenExchangeBase, DOTAccessTokenView):
|
||||
"""
|
||||
Return an error response consisting of the errors in the form
|
||||
"""
|
||||
return Response(status=400, data=form_errors, **kwargs)
|
||||
error_code = form_errors.get('error_code', 400)
|
||||
return Response(status=error_code, data=form_errors, **kwargs)
|
||||
|
||||
|
||||
class LoginWithAccessTokenView(APIView):
|
||||
|
||||
@@ -14,6 +14,7 @@ OAUTH2_TOKEN_ERROR_MALFORMED = 'token_malformed'
|
||||
OAUTH2_TOKEN_ERROR_NONEXISTENT = 'token_nonexistent'
|
||||
OAUTH2_TOKEN_ERROR_NOT_PROVIDED = 'token_not_provided'
|
||||
OAUTH2_USER_NOT_ACTIVE_ERROR = 'user_not_active'
|
||||
OAUTH2_USER_DISABLED_ERROR = 'user_is_disabled'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -91,6 +92,13 @@ class BearerAuthentication(BaseAuthentication):
|
||||
})
|
||||
else:
|
||||
user = token.user
|
||||
has_application = dot_models.Application.objects.filter(user_id=user.id)
|
||||
if not user.has_usable_password() and not has_application:
|
||||
msg = 'User disabled by admin: %s' % user.get_username()
|
||||
raise AuthenticationFailed({
|
||||
'error_code': OAUTH2_USER_DISABLED_ERROR,
|
||||
'developer_message': msg})
|
||||
|
||||
# Check to make sure the users have activated their account (by confirming their email)
|
||||
if not self.allow_inactive_users and not user.is_active:
|
||||
set_custom_attribute("BearerAuthentication_user_active", False)
|
||||
|
||||
@@ -3,7 +3,6 @@ Tests for OAuth2. This module is copied from django-rest-framework-oauth
|
||||
(tests/test_authentication.py) and updated to use our subclass of BearerAuthentication.
|
||||
"""
|
||||
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import unittest
|
||||
@@ -60,7 +59,6 @@ urlpatterns = [
|
||||
@unittest.skipUnless(settings.FEATURES.get("ENABLE_OAUTH2_PROVIDER"), "OAuth2 not enabled")
|
||||
@override_settings(ROOT_URLCONF=__name__)
|
||||
class OAuth2AllowInActiveUsersTests(TestCase):
|
||||
|
||||
OAUTH2_BASE_TESTING_URL = '/oauth2-inactive-test/'
|
||||
|
||||
def setUp(self):
|
||||
@@ -230,3 +228,47 @@ class BearerAuthenticationTests(OAuth2AllowInActiveUsersTests): # pylint: disab
|
||||
# Since this is testing back to previous version, user should be set to true
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
|
||||
|
||||
class OAuthDenyDisabledUsers(OAuth2AllowInActiveUsersTests): # pylint: disable=test-inherits-tests
|
||||
"""
|
||||
To test OAuth on disabled user.
|
||||
"""
|
||||
OAUTH2_BASE_TESTING_URL = '/oauth2-test/'
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# User is active but has have disabled status
|
||||
self.user.is_active = True
|
||||
self.user.set_unusable_password()
|
||||
self.user.save()
|
||||
|
||||
def test_get_form_passing_auth_with_dot(self):
|
||||
"""
|
||||
Asserts response with disabled user with DOT App
|
||||
"""
|
||||
response = self.get_with_bearer_token(self.OAUTH2_BASE_TESTING_URL, token=self.dot_access_token.token)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_post_form_passing_auth(self):
|
||||
"""
|
||||
Asserts response with disabled user with DOT App
|
||||
"""
|
||||
response = self.post_with_bearer_token(self.OAUTH2_BASE_TESTING_URL)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_get_form_without_oauth_app(self):
|
||||
"""
|
||||
Asserts response with disabled user and without DOT APP
|
||||
"""
|
||||
dot_models.Application.objects.filter(user_id=self.user.id).delete()
|
||||
response = self.get_with_bearer_token(self.OAUTH2_BASE_TESTING_URL, token=self.dot_access_token.token)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_post_form_without_oauth_app(self):
|
||||
"""
|
||||
Asserts response with disabled user and without DOT APP
|
||||
"""
|
||||
dot_models.Application.objects.filter(user_id=self.user.id).delete()
|
||||
response = self.post_with_bearer_token(self.OAUTH2_BASE_TESTING_URL)
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
Reference in New Issue
Block a user