diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index 5ed7180637..0b4adbc738 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -115,7 +115,23 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase): # Try to enroll, this should fail. resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': (unicode(self.course.id))})) - self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) + + def test_user_not_activated(self): + # Create a user account, but don't activate it + self.user = UserFactory.create( + username="inactive", + email="inactive@example.com", + password=self.PASSWORD, + is_active=False + ) + + # Log in with the unactivated account + self.client.login(username="inactive", password=self.PASSWORD) + + # Enrollment should succeed, even though we haven't authenticated. + resp = self.client.post(reverse('courseenrollment', kwargs={'course_id': (unicode(self.course.id))})) + self.assertEqual(resp.status_code, 200) def test_unenroll_not_enrolled_in_course(self): # Deactivate the enrollment in the course and verify the URL we get sent to diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index c2e401dd35..35a3fc603d 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -17,6 +17,52 @@ class EnrollmentUserThrottle(UserRateThrottle): rate = '50/second' # TODO Limit significantly after performance testing. +class SessionAuthenticationAllowInactiveUser(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 unactived 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 + 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. + if not user: + return None + + self.enforce_csrf(request) + + # CSRF passed with authenticated user + return (user, None) + + @api_view(['GET']) @authentication_classes((OAuth2Authentication, SessionAuthentication)) @permission_classes((IsAuthenticated,)) @@ -37,7 +83,7 @@ def list_student_enrollments(request): @api_view(['GET', 'POST']) -@authentication_classes((OAuth2Authentication, SessionAuthentication)) +@authentication_classes((OAuth2Authentication, SessionAuthenticationAllowInactiveUser)) @permission_classes((IsAuthenticated,)) @throttle_classes([EnrollmentUserThrottle]) def get_course_enrollment(request, course_id=None):