Implement profile_image upload and remove endpoints
TNL-1537 Co-Authored-By: Andy Armstrong <andya@edx.org> Co-Authored-By: cahrens <christina@edx.org>
This commit is contained in:
82
openedx/core/lib/api/authentication.py
Normal file
82
openedx/core/lib/api/authentication.py
Normal file
@@ -0,0 +1,82 @@
|
||||
""" Common Authentication Handlers used across projects. """
|
||||
from rest_framework import authentication
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from rest_framework.compat import oauth2_provider, provider_now
|
||||
|
||||
|
||||
class SessionAuthenticationAllowInactiveUser(authentication.SessionAuthentication):
|
||||
"""Ensure that the user is logged in, but do not require the account to be active.
|
||||
|
||||
We use this in the special case that a user has created an account,
|
||||
but has not yet activated it. We still want to allow the user to
|
||||
enroll in courses, so we remove the usual restriction
|
||||
on session authentication that requires an active account.
|
||||
|
||||
You should use this authentication class ONLY for end-points that
|
||||
it's safe for an un-activated user to access. For example,
|
||||
we can allow a user to update his/her own enrollments without
|
||||
activating an account.
|
||||
|
||||
"""
|
||||
def authenticate(self, request):
|
||||
"""Authenticate the user, requiring a logged-in account and CSRF.
|
||||
|
||||
This is exactly the same as the `SessionAuthentication` implementation,
|
||||
with the `user.is_active` check removed.
|
||||
|
||||
Args:
|
||||
request (HttpRequest)
|
||||
|
||||
Returns:
|
||||
Tuple of `(user, token)`
|
||||
|
||||
Raises:
|
||||
PermissionDenied: The CSRF token check failed.
|
||||
|
||||
"""
|
||||
# Get the underlying HttpRequest object
|
||||
request = request._request # pylint: disable=protected-access
|
||||
user = getattr(request, 'user', None)
|
||||
|
||||
# Unauthenticated, CSRF validation not required
|
||||
# This is where regular `SessionAuthentication` checks that the user is active.
|
||||
# We have removed that check in this implementation.
|
||||
# But we added a check to prevent anonymous users since we require a logged-in account.
|
||||
if not user or user.is_anonymous():
|
||||
return None
|
||||
|
||||
self.enforce_csrf(request)
|
||||
|
||||
# CSRF passed with authenticated user
|
||||
return (user, None)
|
||||
|
||||
|
||||
class OAuth2AuthenticationAllowInactiveUser(authentication.OAuth2Authentication):
|
||||
"""
|
||||
This is a temporary workaround while the is_active field on the user is coupled
|
||||
with whether or not the user has verified ownership of their claimed email address.
|
||||
Once is_active is decoupled from verified_email, we will no longer need this
|
||||
class override.
|
||||
|
||||
But until then, this authentication class ensures that the user is logged in,
|
||||
but does not require that their account "is_active".
|
||||
|
||||
This class can be used for an OAuth2-accessible endpoint that allows users to access
|
||||
that endpoint without having their email verified. For example, this is used
|
||||
for mobile endpoints.
|
||||
|
||||
"""
|
||||
def authenticate_credentials(self, request, access_token):
|
||||
"""
|
||||
Authenticate the request, given the access token.
|
||||
Override base class implementation to discard failure if user is inactive.
|
||||
"""
|
||||
try:
|
||||
token = oauth2_provider.oauth2.models.AccessToken.objects.select_related('user')
|
||||
# provider_now switches to timezone aware datetime when
|
||||
# the oauth2_provider version supports to it.
|
||||
token = token.get(token=access_token, expires__gt=provider_now())
|
||||
except oauth2_provider.oauth2.models.AccessToken.DoesNotExist:
|
||||
raise AuthenticationFailed('Invalid token')
|
||||
|
||||
return token.user, token
|
||||
@@ -52,9 +52,26 @@ class IsUserInUrl(permissions.BasePermission):
|
||||
Permission that checks to see if the request user matches the user in the URL.
|
||||
"""
|
||||
def has_permission(self, request, view):
|
||||
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
|
||||
# other users, do not let them deduce the existence of an account.
|
||||
"""
|
||||
Returns true if the current request is by the user themselves.
|
||||
|
||||
Note: a 404 is returned for non-staff instead of a 403. This is to prevent
|
||||
users from being able to detect the existence of accounts.
|
||||
"""
|
||||
url_username = request.parser_context.get('kwargs', {}).get('username', '')
|
||||
if request.user.username.lower() != url_username.lower():
|
||||
if request.user.is_staff:
|
||||
return False # staff gets 403
|
||||
raise Http404()
|
||||
return True
|
||||
|
||||
|
||||
class IsUserInUrlOrStaff(IsUserInUrl):
|
||||
"""
|
||||
Permission that checks to see if the request user matches the user in the URL or has is_staff access.
|
||||
"""
|
||||
def has_permission(self, request, view):
|
||||
if request.user.is_staff:
|
||||
return True
|
||||
|
||||
return super(IsUserInUrlOrStaff, self).has_permission(request, view)
|
||||
|
||||
0
openedx/core/lib/api/tests/__init__.py
Normal file
0
openedx/core/lib/api/tests/__init__.py
Normal file
63
openedx/core/lib/api/tests/test_authentication.py
Normal file
63
openedx/core/lib/api/tests/test_authentication.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Tests for util.authentication module."""
|
||||
|
||||
from mock import patch
|
||||
from django.conf import settings
|
||||
from rest_framework import permissions
|
||||
from rest_framework.compat import patterns, url
|
||||
from rest_framework.tests import test_authentication
|
||||
from provider import scope, constants
|
||||
from unittest import skipUnless
|
||||
|
||||
from ..authentication import OAuth2AuthenticationAllowInactiveUser
|
||||
|
||||
|
||||
class OAuth2AuthAllowInactiveUserDebug(OAuth2AuthenticationAllowInactiveUser):
|
||||
"""
|
||||
A debug class analogous to the OAuth2AuthenticationDebug class that tests
|
||||
the OAuth2 flow with the access token sent in a query param."""
|
||||
allow_query_params_token = True
|
||||
|
||||
|
||||
# The following patch overrides the URL patterns for the MockView class used in
|
||||
# rest_framework.tests.test_authentication so that the corresponding AllowInactiveUser
|
||||
# classes are tested instead.
|
||||
@skipUnless(settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER'), 'OAuth2 not enabled')
|
||||
@patch.object(
|
||||
test_authentication,
|
||||
'urlpatterns',
|
||||
patterns(
|
||||
'',
|
||||
url(
|
||||
r'^oauth2-test/$',
|
||||
test_authentication.MockView.as_view(authentication_classes=[OAuth2AuthenticationAllowInactiveUser])
|
||||
),
|
||||
url(
|
||||
r'^oauth2-test-debug/$',
|
||||
test_authentication.MockView.as_view(authentication_classes=[OAuth2AuthAllowInactiveUserDebug])
|
||||
),
|
||||
url(
|
||||
r'^oauth2-with-scope-test/$',
|
||||
test_authentication.MockView.as_view(
|
||||
authentication_classes=[OAuth2AuthenticationAllowInactiveUser],
|
||||
permission_classes=[permissions.TokenHasReadWriteScope]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
class OAuth2AuthenticationAllowInactiveUserTestCase(test_authentication.OAuth2Tests):
|
||||
"""
|
||||
Tests the OAuth2AuthenticationAllowInactiveUser class by running all the existing tests in
|
||||
OAuth2Tests but with the is_active flag on the user set to False.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(OAuth2AuthenticationAllowInactiveUserTestCase, self).setUp()
|
||||
|
||||
# set the user's is_active flag to False.
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
|
||||
# Override the SCOPE_NAME_DICT setting for tests for oauth2-with-scope-test. This is
|
||||
# needed to support READ and WRITE scopes as they currently aren't supported by the
|
||||
# edx-auth2-provider, and their scope values collide with other scopes defined in the
|
||||
# edx-auth2-provider.
|
||||
scope.SCOPE_NAME_DICT = {'read': constants.READ, 'write': constants.WRITE}
|
||||
Reference in New Issue
Block a user