Updated auto_auth endpoint to always return JSON

Defaulting to a plaintext response makes no sense for an endpoint that is intended to be used by machines for testing. The endpoint now returns JSON only unless a redirect action is triggered.
This commit is contained in:
Clinton Blackburn
2017-05-19 16:59:02 -04:00
committed by Clinton Blackburn
parent c285b4f36d
commit 6ff614800c
57 changed files with 272 additions and 441 deletions

View File

@@ -5,13 +5,15 @@ from django.conf import settings
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
from mock import Mock, patch
from mock import patch, Mock
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.locator import CourseLocator
from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_STUDENT, Role
from django_comment_common.models import (
Role, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_STUDENT
)
from django_comment_common.utils import seed_permissions_roles
from student.models import CourseEnrollment, UserProfile, anonymous_id_for_user
from student.models import anonymous_id_for_user, CourseAccessRole, CourseEnrollment, UserProfile
from util.testing import UrlResetMixin
@@ -169,18 +171,16 @@ class AutoAuthEnabledTestCase(AutoAuthTestCase):
course_roles[FORUM_ROLE_MODERATOR],
course_roles[FORUM_ROLE_ADMINISTRATOR]]))
@ddt.data(*COURSE_IDS_DDT)
@ddt.unpack
def test_json_response(self, course_id, course_key): # pylint: disable=unused-argument
"""Verify that we can get JSON back from the auto_auth page."""
response = self._auto_auth(HTTP_ACCEPT='application/json')
def test_json_response(self):
""" The view should return JSON. """
response = self._auto_auth()
response_data = json.loads(response.content)
for key in ['created_status', 'username', 'email', 'password', 'user_id', 'anonymous_id']:
self.assertIn(key, response_data)
user = User.objects.get(username=response_data['username'])
self.assertDictContainsSubset(
{
'created_status': "Logged in",
'created_status': 'Logged in',
'anonymous_id': anonymous_id_for_user(user, None),
},
response_data
@@ -237,23 +237,23 @@ class AutoAuthEnabledTestCase(AutoAuthTestCase):
self.assertTrue(response.url.endswith(url_pattern)) # pylint: disable=no-member
def _auto_auth(self, params=None, status_code=None, **kwargs):
def _auto_auth(self, params=None, status_code=200, **kwargs):
"""
Make a request to the auto-auth end-point and check
that the response is successful.
Arguments:
params (dict): Dict of params to pass to the auto_auth view
status_code (int): Expected response status code
kwargs: Passed directly to the test client's get method.
Returns
response: The response object for the auto_auth page.
Returns:
Response: The response object for the auto_auth page.
"""
params = params or {}
response = self.client.get(self.url, params, **kwargs)
expected_status_code = status_code if status_code else 200
self.assertEqual(response.status_code, expected_status_code)
self.assertEqual(response.status_code, status_code)
# Check that session and CSRF are set in the response
for cookie in ['csrftoken', 'sessionid']:
@@ -270,6 +270,26 @@ class AutoAuthEnabledTestCase(AutoAuthTestCase):
response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
def test_course_access_roles(self):
""" Passing role names via the course_access_roles query string parameter should create CourseAccessRole
objects associated with the user.
"""
expected_roles = ['finance_admin', 'sales_admin']
course_key = CourseLocator.from_string(self.COURSE_ID_SPLIT)
params = {
'course_id': str(course_key),
'course_access_roles': ','.join(expected_roles)
}
response = self._auto_auth(params)
user_info = json.loads(response.content)
for role in expected_roles:
self.assertTrue(
CourseAccessRole.objects.filter(
user__id=user_info['user_id'], course_id=course_key, org=course_key.org, role=role
).exists()
)
class AutoAuthDisabledTestCase(AutoAuthTestCase):
"""

View File

@@ -1,13 +1,14 @@
"""
Student Views
"""
import datetime
import json
import logging
import uuid
import warnings
from collections import defaultdict, namedtuple
from urlparse import parse_qs, urljoin, urlsplit, urlunsplit
from urlparse import parse_qs, urlsplit, urlunsplit
import analytics
import edx_oauth2_provider
@@ -25,7 +26,7 @@ from django.core.validators import ValidationError, validate_email
from django.db import IntegrityError, transaction
from django.db.models.signals import post_save
from django.dispatch import Signal, receiver
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseServerError
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.utils.encoding import force_bytes, force_text
@@ -35,6 +36,7 @@ from django.utils.translation import get_language, ungettext
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.http import require_GET, require_POST
from django.views.generic import TemplateView
from eventtracking import tracker
from ipware.ip import get_ip
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -62,9 +64,8 @@ from certificates.models import ( # pylint: disable=import-error
from course_modes.models import CourseMode
from courseware.access import has_access
from courseware.courses import get_courses, sort_by_announcement, sort_by_start_date # pylint: disable=import-error
from django_comment_common.models import Role
from django_comment_common.models import assign_role
from edxmako.shortcuts import render_to_response, render_to_string
from eventtracking import tracker
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification # pylint: disable=import-error
@@ -99,11 +100,11 @@ from student.helpers import (
)
from student.models import (
ALLOWEDTOENROLL_TO_ENROLLED,
CourseAccessRole,
CourseEnrollment,
CourseEnrollmentAllowed,
CourseEnrollmentAttribute,
DashboardConfiguration,
EnrollStatusChange,
LinkedInAddToProfileConfiguration,
LoginFailures,
LogoutViewConfiguration,
@@ -2131,6 +2132,27 @@ def create_account(request, post_override=None):
return response
def str2bool(s):
s = str(s)
return s.lower() in ('yes', 'true', 't', '1')
def _clean_roles(roles):
""" Clean roles.
Strips whitespace from roles, and removes empty items.
Args:
roles (str[]): List of role names.
Returns:
str[]
"""
roles = [role.strip() for role in roles]
roles = [role for role in roles if role]
return roles
def auto_auth(request):
"""
Create or configure a user account, then log in as that user.
@@ -2154,29 +2176,27 @@ def auto_auth(request):
"""
# Generate a unique name to use if none provided
unique_name = uuid.uuid4().hex[0:30]
generated_username = uuid.uuid4().hex[0:30]
# Use the params from the request, otherwise use these defaults
username = request.GET.get('username', unique_name)
password = request.GET.get('password', unique_name)
email = request.GET.get('email', unique_name + "@example.com")
username = request.GET.get('username', generated_username)
password = request.GET.get('password', username)
email = request.GET.get('email', username + "@example.com")
full_name = request.GET.get('full_name', username)
is_staff = request.GET.get('staff', None)
is_superuser = request.GET.get('superuser', None)
course_id = request.GET.get('course_id', None)
redirect_to = request.GET.get('redirect_to', None)
active_status = request.GET.get('is_active')
is_staff = str2bool(request.GET.get('staff', False))
is_superuser = str2bool(request.GET.get('superuser', False))
course_id = request.GET.get('course_id')
redirect_to = request.GET.get('redirect_to')
is_active = str2bool(request.GET.get('is_active', True))
# mode has to be one of 'honor'/'professional'/'verified'/'audit'/'no-id-professional'/'credit'
# Valid modes: audit, credit, honor, no-id-professional, professional, verified
enrollment_mode = request.GET.get('enrollment_mode', 'honor')
active_status = (not active_status or active_status == 'true')
# Parse roles, stripping whitespace, and filtering out empty strings
roles = _clean_roles(request.GET.get('roles', '').split(','))
course_access_roles = _clean_roles(request.GET.get('course_access_roles', '').split(','))
course_key = None
if course_id:
course_key = CourseLocator.from_string(course_id)
role_names = [v.strip() for v in request.GET.get('roles', '').split(',') if v.strip()]
redirect_when_done = request.GET.get('redirect', '').lower() == 'true' or redirect_to
redirect_when_done = str2bool(request.GET.get('redirect', '')) or redirect_to
login_when_done = 'no_login' not in request.GET
form = AccountCreationForm(
@@ -2199,23 +2219,18 @@ def auto_auth(request):
user = User.objects.get(username=username)
user.email = email
user.set_password(password)
user.is_active = active_status
user.is_active = is_active
user.save()
profile = UserProfile.objects.get(user=user)
reg = Registration.objects.get(user=user)
except PermissionDenied:
return HttpResponseForbidden(_("Account creation not allowed."))
return HttpResponseForbidden(_('Account creation not allowed.'))
# Set the user's global staff bit
if is_staff is not None:
user.is_staff = (is_staff == "true")
user.save()
user.is_staff = is_staff
user.is_superuser = is_superuser
user.save()
if is_superuser is not None:
user.is_superuser = (is_superuser == "true")
user.save()
if active_status:
if is_active:
reg.activate()
reg.save()
@@ -2226,13 +2241,17 @@ def auto_auth(request):
profile.save()
# Enroll the user in a course
if course_key is not None:
course_key = None
if course_id:
course_key = CourseLocator.from_string(course_id)
CourseEnrollment.enroll(user, course_key, mode=enrollment_mode)
# Apply the roles
for role_name in role_names:
role = Role.objects.get(name=role_name, course_id=course_key)
user.roles.add(role)
# Apply the roles
for role in roles:
assign_role(course_key, user, role)
for role in course_access_roles:
CourseAccessRole.objects.update_or_create(user=user, course_id=course_key, org=course_key.org, role=role)
# Log in as the user
if login_when_done:
@@ -2241,50 +2260,33 @@ def auto_auth(request):
create_comments_service_user(user)
# Provide the user with a valid CSRF token
# then return a 200 response unless redirect is true
if redirect_when_done:
# Redirect to specific page if specified
if redirect_to:
# Redirect to page specified by the client
redirect_url = redirect_to
# Redirect to course home page if course_id is known
elif course_id:
# Redirect to the course homepage (in LMS) or outline page (in Studio)
try:
# redirect to course home page in LMS
redirect_url = reverse(
course_home_url_name(request),
kwargs={'course_id': course_id}
)
redirect_url = reverse(course_home_url_name(request), kwargs={'course_id': course_id})
except NoReverseMatch:
# redirect to course outline page in Studio
redirect_url = reverse(
'course_handler',
kwargs={'course_key_string': course_id}
)
redirect_url = reverse('course_handler', kwargs={'course_key_string': course_id})
else:
# Redirect to the learner dashboard (in LMS) or homepage (in Studio)
try:
# redirect to dashboard for LMS
redirect_url = reverse('dashboard')
except NoReverseMatch:
# redirect to home for Studio
redirect_url = reverse('home')
return redirect(redirect_url)
elif request.META.get('HTTP_ACCEPT') == 'application/json':
else:
response = JsonResponse({
'created_status': u"Logged in" if login_when_done else "Created",
'created_status': 'Logged in' if login_when_done else 'Created',
'username': username,
'email': email,
'password': password,
'user_id': user.id, # pylint: disable=no-member
'anonymous_id': anonymous_id_for_user(user, None),
})
else:
success_msg = u"{} user {} ({}) with password {} and user_id {}".format(
u"Logged in" if login_when_done else "Created",
username, email, password, user.id # pylint: disable=no-member
)
response = HttpResponse(success_msg)
response.set_cookie('csrftoken', csrf(request)['csrf_token'])
return response