Commit 1d8ff75b by Joe Blaylock Committed by stv

Deny self-service certificates for anonymous users

Bugfix for the following condition:
When anonymous (e.g., non-registered) access is enabled for a course,
and that course also uses the /request_certificate endpoint (e.g., via
the "Grade Me" button), anonymous users could request to be graded and
given certificates.

As a matter of policy, Stanford doesn't want to give certificates to
anonymous students.

Also adds defensive error checks in several more places.
parent bfc5c4c8
...@@ -2,23 +2,21 @@ ...@@ -2,23 +2,21 @@
metadata: metadata:
display_name: (Grade Me!) Button display_name: (Grade Me!) Button
data: | data: |
<p>By clicking the button below, you assert that you have completed the course in its entirety.</p> <p id="verify_button_by_clicking_msg">By clicking the button below, you assert that you have completed the course in its entirety.</p>
<!-- <form action=/request_certificate method=POST> <p><input type=button value="Yes, I Agree." id="User_Verify_Button" style="margin-bottom: 20px;" /></p>
<input type=hidden name=student_id value=103 />
<input type=hidden name=course_id value="OpenEdX/200/Stanford_Sandbox" />
<input type=submit name="hoo" />
</form> -->
<input type=button value="Yes, I Agree." id="User_Verify_Button" style="margin-bottom: 20px;" />
<p class="verify-button-success-text" style="font-weight: bold; color: #008200;"></p> <p class="verify-button-success-text" style="font-weight: bold; color: #008200;"></p>
<script type="text/javascript"> <script type="text/javascript">
var success_message = "Your grading and certification request has been received, <br />if you have passed, your certificate should be available in the next 20 minutes."; var success_message = "Your grading and certification request has been received, <br />if you have passed, your certificate should be available in the next 20 minutes.";
var failure_message = "We're sorry; users who haven't created accounts and registered for the course may not receive Statements of Accomplishment.";
// for actual value of username, use scraped_username.split(':')[1].trim(); to get actual value
var scraped_username = $('li.primary a.user-link').text();
if (scraped_username) {
document.getElementById('User_Verify_Button').addEventListener("click", document.getElementById('User_Verify_Button').addEventListener("click",
function(event) { function(event) {
(function(event) { (function(event) {
var linkcontents = $('a.user-link').contents();
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/request_certificate', url: '/request_certificate',
...@@ -29,4 +27,8 @@ data: | ...@@ -29,4 +27,8 @@ data: |
}); });
}).call(document.getElementById('User_Verify_Button'), event); }).call(document.getElementById('User_Verify_Button'), event);
}); });
} else {
$('#verify_button_by_clicking_msg').html(failure_message);
$('#User_Verify_Button').remove();
};
</script> </script>
"""URL handlers related to certificate handling by LMS""" """URL handlers related to certificate handling by LMS"""
from dogapi import dog_stats_api from dogapi import dog_stats_api
import json import json
import logging import logging
...@@ -9,16 +10,13 @@ from django.http import HttpResponse ...@@ -9,16 +10,13 @@ from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from capa.xqueue_interface import XQUEUE_METRIC_NAME from capa.xqueue_interface import XQUEUE_METRIC_NAME
from certificates.models import certificate_status_for_student, CertificateStatuses, GeneratedCertificate from student.models import UserProfile
from certificates.queue import XQueueCertInterface
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.locations import SlashSeparatedCourseKey from xmodule.modulestore.locations import SlashSeparatedCourseKey
from certificates.models import certificate_status_for_student, CertificateStatuses, GeneratedCertificate from certificates.models import certificate_status_for_student, CertificateStatuses, GeneratedCertificate
from certificates.queue import XQueueCertInterface from certificates.queue import XQueueCertInterface
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.django import modulestore
use_cme = settings.FEATURES.get('USE_CME_REGISTRATION', False) use_cme = settings.FEATURES.get('USE_CME_REGISTRATION', False)
if use_cme: if use_cme:
...@@ -37,10 +35,24 @@ def request_certificate(request): ...@@ -37,10 +35,24 @@ def request_certificate(request):
at the end of a course run, so that we can be sure users get graded and at the end of a course run, so that we can be sure users get graded and
then if and only if they pass, do they get a certificate issued. then if and only if they pass, do they get a certificate issued.
""" """
if request.method == "POST": # Memoize user information; return error if it's invalid
if request.user.is_authenticated(): user = None
xqci = XQueueCertInterface() username = ''
username = request.user.username try:
user = request.user
username = user.username
except AttributeError:
return HttpResponse(json.dumps({'add_status': 'error', 'error': 'ERRORBADREQUEST'}), mimetype='application/json')
# It is an error to hit this endpoint with anything but a POST
if request.method != "POST":
return HttpResponse(json.dumps({'add_status': 'error', 'error': 'ERRORNOPOST'}), mimetype='application/json')
# It is an error to hit this endpoint as an anonymous/nonregistered user
if not (user.is_authenticated() and UserProfile.has_registered(user)):
return HttpResponse(json.dumps({'add_status': 'error', 'error': 'ERRORANONYMOUSUSER'}), mimetype='application/json')
xq = XQueueCertInterface()
student = User.objects.get(username=username) student = User.objects.get(username=username)
course_key = SlashSeparatedCourseKey.from_deprecated_string(request.POST.get('course_id')) course_key = SlashSeparatedCourseKey.from_deprecated_string(request.POST.get('course_id'))
course = modulestore().get_course(course_key, depth=2) course = modulestore().get_course(course_key, depth=2)
...@@ -48,15 +60,13 @@ def request_certificate(request): ...@@ -48,15 +60,13 @@ def request_certificate(request):
if use_cme: if use_cme:
titlelist = CmeUserProfile.objects.filter(user=student).values('professional_designation') titlelist = CmeUserProfile.objects.filter(user=student).values('professional_designation')
if len(titlelist): if len(titlelist):
print "DEBUG: {}".format(repr(titlelist))
title = titlelist[0]['professional_designation'] title = titlelist[0]['professional_designation']
status = certificate_status_for_student(student, course_key)['status'] status = certificate_status_for_student(student, course_key)['status']
if status in [CertificateStatuses.unavailable, CertificateStatuses.notpassing, CertificateStatuses.error]: if status in [CertificateStatuses.unavailable, CertificateStatuses.notpassing, CertificateStatuses.error]:
logger.info('Grading and certification requested for user {} in course {} via /request_certificate call'.format(username, course_key)) logger.info('Grading and certification requested for user {} in course {} via /request_certificate call'.format(username, course_key))
status = xqci.add_cert(student, course_key, course=course) status = xq.add_cert(student, course_key, course=course, title=title)
return HttpResponse(json.dumps({'add_status': status}), mimetype='application/json') return HttpResponse(json.dumps({'add_status': status, 'error': ''}), mimetype='application/json')
return HttpResponse(json.dumps({'add_status': 'ERRORANONYMOUSUSER'}), mimetype='application/json')
@csrf_exempt @csrf_exempt
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment