Check for data download permission in report endpoints

This commit is contained in:
Dave St.Germain
2019-11-18 12:47:41 -05:00
parent 8b815b802b
commit 3d82ce18f9
5 changed files with 54 additions and 16 deletions

View File

@@ -0,0 +1,28 @@
"""
Django rules for student roles
"""
from __future__ import absolute_import
import rules
from lms.djangoapps.courseware.access import has_access
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace
from .roles import CourseDataResearcherRole
# Waffle flag to enable the separate course outline page and full width content.
RESEARCHER_ROLE = CourseWaffleFlag(WaffleFlagNamespace(name='instructor'), 'researcher')
@rules.predicate
def can_access_reports(user, course_id):
"""
Returns whether the user can access the course data downloads.
"""
is_staff = user.is_staff
if RESEARCHER_ROLE.is_enabled(course_id):
return is_staff or CourseDataResearcherRole(course_id).has_user(user)
else:
return is_staff or has_access(user, 'staff', course_id)
rules.add_perm('student.can_research', can_access_reports)

View File

@@ -591,7 +591,9 @@ class DataDownloadsTest(BaseInstructorDashboardTest):
def setUp(self):
super(DataDownloadsTest, self).setUp()
self.course_fixture = CourseFixture(**self.course_info).install()
self.instructor_username, self.instructor_id, __, __ = self.log_in_as_instructor()
self.instructor_username, self.instructor_id, __, __ = self.log_in_as_instructor(
course_access_roles=['data_researcher']
)
instructor_dashboard_page = self.visit_instructor_dashboard()
self.data_download_section = instructor_dashboard_page.select_data_download()

View File

@@ -95,7 +95,10 @@ from student.models import (
get_retired_email_by_email,
get_retired_username_by_username
)
from student.roles import CourseBetaTesterRole, CourseFinanceAdminRole, CourseInstructorRole, CourseSalesAdminRole
from student.roles import (
CourseBetaTesterRole, CourseDataResearcherRole, CourseFinanceAdminRole,
CourseInstructorRole, CourseSalesAdminRole
)
from student.tests.factories import AdminFactory, UserFactory
from xmodule.fields import Date
from xmodule.modulestore import ModuleStoreEnum
@@ -555,6 +558,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
staff_member = StaffFactory(course_key=self.course.id)
CourseEnrollment.enroll(staff_member, self.course.id)
CourseFinanceAdminRole(self.course.id).add_users(staff_member)
CourseDataResearcherRole(self.course.id).add_users(staff_member)
self.client.login(username=staff_member.username, password='test')
# Try to promote to forums admin - not working
# update_forum_role(self.course.id, staff_member, FORUM_ROLE_ADMINISTRATOR, 'allow')
@@ -593,6 +597,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
CourseEnrollment.enroll(inst, self.course.id)
CourseFinanceAdminRole(self.course.id).add_users(inst)
CourseDataResearcherRole(self.course.id).add_users(inst)
self.client.login(username=inst.username, password='test')
for endpoint, args in self.staff_level_endpoints:
@@ -2602,6 +2607,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
min_price=40)
self.course_mode.save()
self.instructor = InstructorFactory(course_key=self.course.id)
CourseDataResearcherRole(self.course.id).add_users(self.instructor)
self.client.login(username=self.instructor.username, password='test')
self.cart = Order.get_cart_for_user(self.instructor)
self.coupon_code = 'abcde'
@@ -3038,6 +3044,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
'max_size': 2, 'topics': [{'id': 'topic', 'name': 'Topic', 'description': 'A Topic'}]
}))
course_instructor = InstructorFactory(course_key=self.course.id)
CourseDataResearcherRole(self.course.id).add_users(course_instructor)
self.client.login(username=course_instructor.username, password='test')
url = reverse('get_students_features', kwargs={'course_id': text_type(self.course.id)})

View File

@@ -234,7 +234,7 @@ def require_post_params(*args, **kwargs):
return decorator
def require_level(level):
def require_level(level, perm=None):
"""
Decorator with argument that requires an access level of the requesting
user. If the requirement is not satisfied, returns an
@@ -255,7 +255,8 @@ def require_level(level):
request = args[0]
course = get_course_by_id(CourseKey.from_string(kwargs['course_id']))
if has_access(request.user, level, course):
if has_access(request.user, level, course) and \
((perm and request.user.has_perm(perm, course.id)) or not perm):
return func(*args, **kwargs)
else:
return HttpResponseForbidden()
@@ -1047,7 +1048,7 @@ def list_course_role_members(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@common_exceptions_400
def get_problem_responses(request, course_id):
"""
@@ -1107,7 +1108,7 @@ def get_grading_config(request, course_id):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
def get_sale_records(request, course_id, csv=False): # pylint: disable=unused-argument, redefined-outer-name
"""
return the summary of all sales records for a particular course
@@ -1138,7 +1139,7 @@ def get_sale_records(request, course_id, csv=False): # pylint: disable=unused-a
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
def get_sale_order_records(request, course_id): # pylint: disable=unused-argument
"""
return the summary of all sales records for a particular course
@@ -1286,7 +1287,7 @@ def get_issued_certificates(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@common_exceptions_400
def get_students_features(request, course_id, csv=False): # pylint: disable=redefined-outer-name
"""
@@ -1933,7 +1934,7 @@ def spent_registration_codes(request, course_id):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
def get_anon_ids(request, course_id): # pylint: disable=unused-argument
"""
Respond with 2-column CSV output of user-id, anonymized-user-id
@@ -2564,7 +2565,7 @@ def list_report_downloads(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@require_finance_admin
def list_financial_report_downloads(_request, course_id):
"""
@@ -2586,7 +2587,7 @@ def list_financial_report_downloads(_request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@common_exceptions_400
def export_ora2_data(request, course_id):
"""
@@ -2604,7 +2605,7 @@ def export_ora2_data(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@common_exceptions_400
def calculate_grades_csv(request, course_id):
"""
@@ -2622,7 +2623,7 @@ def calculate_grades_csv(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_level('staff', perm='student.can_research')
@common_exceptions_400
def problem_grade_report(request, course_id):
"""

View File

@@ -57,7 +57,7 @@ from openedx.core.lib.xblock_utils import wrap_xblock
from shoppingcart.models import Coupon, CourseRegCodeItem, PaidCourseRegistration
from student.models import CourseEnrollment
from student.roles import (
CourseDataResearcherRole, CourseFinanceAdminRole, CourseInstructorRole,
CourseFinanceAdminRole, CourseInstructorRole,
CourseSalesAdminRole, CourseStaffRole
)
from util.json_request import JsonResponse
@@ -121,7 +121,7 @@ def instructor_dashboard_2(request, course_id):
'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user),
'staff': bool(has_access(request.user, 'staff', course)),
'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
'data_researcher': CourseDataResearcherRole(course_key).has_user(request.user),
'data_researcher': request.user.has_perm('student.can_research', course_key),
}
if not access['staff']:
@@ -713,7 +713,7 @@ def _section_data_download(course, access):
),
'export_ora2_data_url': reverse('export_ora2_data', kwargs={'course_id': six.text_type(course_key)}),
}
if not (access.get('data_researcher') or access.get('admin')):
if not access.get('data_researcher'):
section_data['is_hidden'] = True
return section_data