diff --git a/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py b/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py index ff66c1d9f0c4..14baf7b6d4bc 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py +++ b/cms/djangoapps/contentstore/rest_api/v0/tests/test_advanced_settings.py @@ -11,7 +11,7 @@ from django.urls import reverse from milestones.tests.utils import MilestonesTestCaseMixin from openedx_authz.api.users import assign_role_to_user_in_scope -from openedx_authz.constants.roles import COURSE_STAFF +from openedx_authz.constants.roles import COURSE_AUDITOR, COURSE_EDITOR, COURSE_STAFF from openedx_authz.engine.enforcer import AuthzEnforcer from openedx_authz.engine.utils import migrate_policy_between_enforcers from rest_framework.test import APIClient @@ -203,3 +203,67 @@ def test_patch_unauthorized_for_specific_course(self, mock_flag): content_type="application/json" ) self.assertEqual(response.status_code, 403) # noqa: PT009 + + def test_editor_can_view_advanced_settings(self, mock_flag): + """Course editor (view-only for advanced settings) can GET.""" + editor_user = UserFactory() + assign_role_to_user_in_scope( + editor_user.username, + COURSE_EDITOR.external_key, + str(self.course.id), + ) + + editor_client = APIClient() + editor_client.force_authenticate(user=editor_user) + response = editor_client.get(self.url) + assert response.status_code == 200 + + def test_editor_cannot_write_advanced_settings(self, mock_flag): + """Course editor cannot PATCH advanced settings (requires manage permission).""" + editor_user = UserFactory() + assign_role_to_user_in_scope( + editor_user.username, + COURSE_EDITOR.external_key, + str(self.course.id), + ) + + editor_client = APIClient() + editor_client.force_authenticate(user=editor_user) + response = editor_client.patch( + self.url, + {"display_name": {"value": "Test"}}, + content_type="application/json", + ) + assert response.status_code == 403 + + def test_auditor_can_view_advanced_settings(self, mock_flag): + """Course auditor (view-only) can GET advanced settings.""" + auditor_user = UserFactory() + assign_role_to_user_in_scope( + auditor_user.username, + COURSE_AUDITOR.external_key, + str(self.course.id), + ) + + auditor_client = APIClient() + auditor_client.force_authenticate(user=auditor_user) + response = auditor_client.get(self.url) + assert response.status_code == 200 + + def test_auditor_cannot_write_advanced_settings(self, mock_flag): + """Course auditor cannot PATCH advanced settings (requires manage permission).""" + auditor_user = UserFactory() + assign_role_to_user_in_scope( + auditor_user.username, + COURSE_AUDITOR.external_key, + str(self.course.id), + ) + + auditor_client = APIClient() + auditor_client.force_authenticate(user=auditor_user) + response = auditor_client.patch( + self.url, + {"display_name": {"value": "Test"}}, + content_type="application/json", + ) + assert response.status_code == 403 diff --git a/common/djangoapps/student/auth.py b/common/djangoapps/student/auth.py index 30e543c2f4b1..b17b5345e43e 100644 --- a/common/djangoapps/student/auth.py +++ b/common/djangoapps/student/auth.py @@ -11,7 +11,10 @@ from django.core.exceptions import PermissionDenied from opaque_keys.edx.locator import LibraryLocator from openedx_authz import api as authz_api -from openedx_authz.constants.permissions import COURSES_MANAGE_ADVANCED_SETTINGS +from openedx_authz.constants.permissions import ( + COURSES_MANAGE_ADVANCED_SETTINGS, + COURSES_VIEW_ADVANCED_SETTINGS, +) from common.djangoapps.student.roles import ( CourseBetaTesterRole, @@ -209,8 +212,14 @@ def check_course_advanced_settings_access(user, course_key, access_type='read'): ): # When feature is disabled, only staff/superuser can access (bypass authz) return user.is_staff or user.is_superuser - # Otherwise check authz permission - return authz_api.is_user_allowed(user.username, COURSES_MANAGE_ADVANCED_SETTINGS.identifier, str(course_key)) + + # Read requires view permission; all other access types require manage. + if access_type == 'read': + permission = COURSES_VIEW_ADVANCED_SETTINGS.identifier + else: + permission = COURSES_MANAGE_ADVANCED_SETTINGS.identifier + + return authz_api.is_user_allowed(user.username, permission, str(course_key)) # Legacy permission checks if access_type == 'read':