Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editor and Auditor tests are very similar. Could we use ddt to avoid code repetition?

"""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
15 changes: 12 additions & 3 deletions common/djangoapps/student/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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':
Expand Down
Loading