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:
jsa
2015-03-09 11:29:42 -04:00
committed by Andy Armstrong
parent 97e44ed20f
commit 46b63164a0
31 changed files with 1135 additions and 174 deletions

View 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

View File

@@ -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)

View File

View 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}