Merge pull request #21269 from cpennington/requires-level-permissions

Add a decorator that checks for course-level permissions
This commit is contained in:
Calen Pennington
2019-08-06 15:09:43 -04:00
committed by GitHub
13 changed files with 91 additions and 25 deletions

View File

@@ -14,7 +14,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from bulk_enroll.serializers import BulkEnrollmentSerializer
from instructor.views.api import students_update_enrollment
from lms.djangoapps.instructor.views.api import students_update_enrollment
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort, get_cohort_by_name
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from openedx.core.djangoapps.enrollments.views import EnrollmentUserThrottle

View File

@@ -10,7 +10,7 @@ from django.db import migrations
from django.http import Http404
from courseware.courses import get_course_by_id
from instructor.access import allow_access, revoke_access
from lms.djangoapps.instructor.access import allow_access, revoke_access
log = logging.getLogger("edx.ccx")

View File

@@ -11,7 +11,7 @@ from opaque_keys.edx.locator import BlockUsageLocator
from six import text_type
from courseware import models
from instructor_analytics.csvs import create_csv_response
from lms.djangoapps.instructor_analytics.csvs import create_csv_response
from util.json_request import JsonResponse
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.inheritance import own_metadata

View File

@@ -19,7 +19,7 @@ from rest_framework.viewsets import ViewSet
from six import text_type
from discussion.views import get_divided_discussions
from instructor.access import update_forum_role
from lms.djangoapps.instructor.access import update_forum_role
from lms.djangoapps.discussion.django_comment_client.utils import available_division_schemes
from lms.djangoapps.discussion.rest_api.api import (
create_comment,

View File

@@ -0,0 +1,30 @@
"""
Permissions for the instructor dashboard and associated actions
"""
from bridgekeeper import perms
from bridgekeeper.rules import is_staff
from courseware.rules import HasAccessRule
ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM = 'instructor.allow_student_to_bypass_entrance_exam'
ASSIGN_TO_COHORTS = 'instructor.assign_to_cohorts'
EDIT_COURSE_ACCESS = 'instructor.edit_course_access'
EDIT_FORUM_ROLES = 'instructor.edit_forum_roles'
EDIT_INVOICE_VALIDATION = 'instructor.edit_invoice_validation'
ENABLE_CERTIFICATE_GENERATION = 'instructor.enable_certificate_generation'
GENERATE_CERTIFICATE_EXCEPTIONS = 'instructor.generate_certificate_exceptions'
GENERATE_BULK_CERTIFICATE_EXCEPTIONS = 'instructor.generate_bulk_certificate_exceptions'
GIVE_STUDENT_EXTENSION = 'instructor.give_student_extension'
VIEW_ISSUED_CERTIFICATES = 'instructor.view_issued_certificates'
perms[ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM] = HasAccessRule('staff')
perms[ASSIGN_TO_COHORTS] = HasAccessRule('staff')
perms[EDIT_COURSE_ACCESS] = HasAccessRule('instructor')
perms[EDIT_FORUM_ROLES] = HasAccessRule('staff')
perms[EDIT_INVOICE_VALIDATION] = HasAccessRule('staff')
perms[ENABLE_CERTIFICATE_GENERATION] = is_staff
perms[GENERATE_CERTIFICATE_EXCEPTIONS] = is_staff
perms[GENERATE_BULK_CERTIFICATE_EXCEPTIONS] = is_staff
perms[GIVE_STUDENT_EXTENSION] = HasAccessRule('staff')
perms[VIEW_ISSUED_CERTIFICATES] = HasAccessRule('staff')

View File

@@ -148,6 +148,20 @@ from .tools import (
strip_if_string
)
from ..permissions import (
ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM,
ASSIGN_TO_COHORTS,
EDIT_COURSE_ACCESS,
EDIT_FORUM_ROLES,
EDIT_INVOICE_VALIDATION,
ENABLE_CERTIFICATE_GENERATION,
GENERATE_CERTIFICATE_EXCEPTIONS,
GENERATE_BULK_CERTIFICATE_EXCEPTIONS,
GIVE_STUDENT_EXTENSION,
VIEW_ISSUED_CERTIFICATES,
)
log = logging.getLogger(__name__)
TASK_SUBMISSION_OK = 'created'
@@ -249,6 +263,28 @@ def require_level(level):
return decorator
def require_course_permission(permission):
"""
Decorator with argument that requires a specific permission of the requesting
user. If the requirement is not satisfied, returns an
HttpResponseForbidden (403).
Assumes that request is in args[0].
Assumes that course_id is in kwargs['course_id'].
"""
def decorator(func): # pylint: disable=missing-docstring
def wrapped(*args, **kwargs):
request = args[0]
course = get_course_by_id(CourseKey.from_string(kwargs['course_id']))
if request.user.has_perm(permission, course):
return func(*args, **kwargs)
else:
return HttpResponseForbidden()
return wrapped
return decorator
def require_sales_admin(func):
"""
Decorator for checking sales administrator access before executing an HTTP endpoint. This decorator
@@ -877,7 +913,7 @@ def bulk_beta_modify_access(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('instructor')
@require_course_permission(EDIT_COURSE_ACCESS)
@require_post_params(
unique_student_identifier="email or username of user to change access",
rolename="'instructor', 'staff', 'beta', or 'ccx_coach'",
@@ -1143,7 +1179,7 @@ def get_sale_order_records(request, course_id): # pylint: disable=unused-argume
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_order_records.csv", csv_columns, datarows)
@require_level('staff')
@require_course_permission(EDIT_INVOICE_VALIDATION)
@require_POST
def sale_validation(request, course_id):
"""
@@ -1210,7 +1246,7 @@ def re_validate_invoice(obj_invoice):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_course_permission(VIEW_ISSUED_CERTIFICATES)
def get_issued_certificates(request, course_id):
"""
Responds with JSON if CSV is not required. contains a list of issued certificates.
@@ -1388,7 +1424,7 @@ def _cohorts_csv_validator(file_storage, file_to_validate):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_POST
@require_level('staff')
@require_course_permission(ASSIGN_TO_COHORTS)
@common_exceptions_400
def add_users_to_cohorts(request, course_id):
"""
@@ -2758,7 +2794,7 @@ def send_email(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_course_permission(EDIT_FORUM_ROLES)
@require_post_params(
unique_student_identifier="email or username of user to change access",
rolename="the forum role",
@@ -2851,7 +2887,7 @@ def _display_unit(unit):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_course_permission(GIVE_STUDENT_EXTENSION)
@require_post_params('student', 'url', 'due_datetime')
def change_due_date(request, course_id):
"""
@@ -2875,7 +2911,7 @@ def change_due_date(request, course_id):
@require_POST
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_course_permission(GIVE_STUDENT_EXTENSION)
@require_post_params('student', 'url')
def reset_due_date(request, course_id):
"""
@@ -2986,7 +3022,7 @@ def generate_example_certificates(request, course_id=None): # pylint: disable=u
return redirect(_instructor_dash_url(course_key, section='certificates'))
@require_global_staff
@require_course_permission(ENABLE_CERTIFICATE_GENERATION)
@require_POST
def enable_certificate_generation(request, course_id=None):
"""Enable/disable self-generated certificates for a course.
@@ -3006,7 +3042,7 @@ def enable_certificate_generation(request, course_id=None):
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_course_permission(ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM)
@require_POST
def mark_student_can_skip_entrance_exam(request, course_id):
"""
@@ -3270,7 +3306,7 @@ def get_student(username_or_email, course_key):
@transaction.non_atomic_requests
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(GENERATE_CERTIFICATE_EXCEPTIONS)
@require_POST
@common_exceptions_400
def generate_certificate_exceptions(request, course_id, generate_for=None):
@@ -3312,7 +3348,7 @@ def generate_certificate_exceptions(request, course_id, generate_for=None):
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_global_staff
@require_course_permission(GENERATE_BULK_CERTIFICATE_EXCEPTIONS)
@require_POST
def generate_bulk_certificate_exceptions(request, course_id):
"""

View File

@@ -20,7 +20,7 @@ from six.moves import range, zip
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from courseware.tests.factories import InstructorFactory
from instructor_analytics.basic import (
from lms.djangoapps.instructor_analytics.basic import (
AVAILABLE_FEATURES,
PROFILE_FEATURES,
STUDENT_FEATURES,

View File

@@ -6,7 +6,7 @@ import pytest
from django.test import TestCase
from six.moves import range
from instructor_analytics.csvs import create_csv_response, format_dictlist, format_instances
from lms.djangoapps.instructor_analytics.csvs import create_csv_response, format_dictlist, format_instances
class TestAnalyticsCSVS(TestCase):

View File

@@ -6,7 +6,7 @@ from django.test import TestCase
from opaque_keys.edx.locator import CourseLocator
from six.moves import range
from instructor_analytics.distributions import AVAILABLE_PROFILE_FEATURES, profile_distribution
from lms.djangoapps.instructor_analytics.distributions import AVAILABLE_PROFILE_FEATURES, profile_distribution
from student.models import CourseEnrollment
from student.tests.factories import UserFactory

View File

@@ -14,8 +14,8 @@ from pytz import UTC
from courseware.courses import get_course_by_id
from edxmako.shortcuts import render_to_string
from instructor_analytics.basic import enrolled_students_features, list_may_enroll
from instructor_analytics.csvs import format_dictlist
from lms.djangoapps.instructor_analytics.basic import enrolled_students_features, list_may_enroll
from lms.djangoapps.instructor_analytics.csvs import format_dictlist
from lms.djangoapps.instructor.paidcourse_enrollment_report import PaidCourseEnrollmentReportProvider
from lms.djangoapps.instructor_task.models import ReportStore
from shoppingcart.models import (

View File

@@ -22,8 +22,8 @@ from six.moves import zip, zip_longest
from course_blocks.api import get_course_blocks
from courseware.courses import get_course_by_id
from courseware.user_state_client import DjangoXBlockUserStateClient
from instructor_analytics.basic import list_problem_responses
from instructor_analytics.csvs import format_dictlist
from lms.djangoapps.instructor_analytics.basic import list_problem_responses
from lms.djangoapps.instructor_analytics.csvs import format_dictlist
from lms.djangoapps.certificates.models import CertificateWhitelist, GeneratedCertificate, certificate_info_for_user
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.grades.api import context as grades_context

View File

@@ -18,8 +18,8 @@ from django.core.files.storage import DefaultStorage
from openassessment.data import OraAggregateData
from pytz import UTC
from instructor_analytics.basic import get_proctored_exam_results
from instructor_analytics.csvs import format_dictlist
from lms.djangoapps.instructor_analytics.basic import get_proctored_exam_results
from lms.djangoapps.instructor_analytics.csvs import format_dictlist
from openedx.core.djangoapps.course_groups.cohorts import add_user_to_cohort
from openedx.core.djangoapps.course_groups.models import CourseUserGroup
from survey.models import SurveyAnswer

View File

@@ -33,7 +33,7 @@ from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from courseware.tests.factories import InstructorFactory
from instructor_analytics.basic import UNAVAILABLE, list_problem_responses
from lms.djangoapps.instructor_analytics.basic import UNAVAILABLE, list_problem_responses
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
from lms.djangoapps.certificates.tests.factories import CertificateWhitelistFactory, GeneratedCertificateFactory
from lms.djangoapps.grades.models import PersistentCourseGrade