|
7 | 7 |
|
8 | 8 | import ddt |
9 | 9 | import factory |
| 10 | + |
10 | 11 | from django.conf import settings |
11 | 12 | from django.contrib.auth import get_user_model |
12 | 13 | from django.test.utils import override_settings |
13 | 14 | from django.urls import reverse |
14 | 15 | from rest_framework import status |
15 | 16 | from rest_framework.test import APITestCase |
| 17 | +from rest_framework.test import APIClient |
| 18 | +from openedx.core.djangoapps.authz.tests.mixins import CourseAuthzTestMixin |
| 19 | +from openedx_authz.constants.roles import COURSE_STAFF, COURSE_DATA_RESEARCHER |
16 | 20 |
|
17 | 21 | from common.djangoapps.course_modes.models import CourseMode |
18 | 22 | from common.djangoapps.course_modes.tests.factories import CourseModeFactory |
19 | 23 | from common.djangoapps.student.tests.factories import StaffFactory, UserFactory |
20 | 24 | from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase |
21 | 25 | from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory |
| 26 | +from cms.djangoapps.contentstore.api.tests.base import BaseCourseViewTest |
22 | 27 |
|
23 | 28 | User = get_user_model() |
24 | 29 |
|
@@ -272,3 +277,81 @@ def test_list_ready_to_update_reference_success(self, mock_block, mock_auth): |
272 | 277 | {'usage_key': str(self.block2.location)}, |
273 | 278 | ]) |
274 | 279 | mock_auth.assert_called_once() |
| 280 | + |
| 281 | + |
| 282 | +class CourseValidationAuthzTest(CourseAuthzTestMixin, BaseCourseViewTest): |
| 283 | + """ |
| 284 | + Tests Course Validation API authorization using openedx-authz. |
| 285 | + The endpoint uses COURSES_VIEW_COURSE permission. |
| 286 | + """ |
| 287 | + |
| 288 | + view_name = "courses_api:course_validation" |
| 289 | + authz_roles_to_assign = [COURSE_STAFF.external_key] |
| 290 | + |
| 291 | + def test_authorized_user_can_access(self): |
| 292 | + """ |
| 293 | + User with COURSE_STAFF role should be allowed via AuthZ. |
| 294 | + """ |
| 295 | + resp = self.authorized_client.get(self.get_url(self.course_key)) |
| 296 | + |
| 297 | + self.assertEqual(resp.status_code, status.HTTP_200_OK) |
| 298 | + |
| 299 | + def test_unauthorized_user_cannot_access(self): |
| 300 | + """ |
| 301 | + User without permissions should be denied. |
| 302 | + """ |
| 303 | + resp = self.unauthorized_client.get(self.get_url(self.course_key)) |
| 304 | + |
| 305 | + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) |
| 306 | + |
| 307 | + def test_role_scoped_to_course(self): |
| 308 | + """ |
| 309 | + Authorization should only apply to the assigned course scope. |
| 310 | + """ |
| 311 | + other_course = self.store.create_course( |
| 312 | + "OtherOrg", |
| 313 | + "OtherCourse", |
| 314 | + "Run", |
| 315 | + self.staff.id, |
| 316 | + ) |
| 317 | + |
| 318 | + resp = self.authorized_client.get(self.get_url(other_course.id)) |
| 319 | + |
| 320 | + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) |
| 321 | + |
| 322 | + def test_staff_user_allowed_via_legacy(self): |
| 323 | + """ |
| 324 | + Course staff should pass through legacy fallback when AuthZ denies. |
| 325 | + """ |
| 326 | + self.client.login(username=self.staff.username, password=self.password) |
| 327 | + |
| 328 | + resp = self.client.get(self.get_url(self.course_key)) |
| 329 | + |
| 330 | + self.assertEqual(resp.status_code, status.HTTP_200_OK) |
| 331 | + |
| 332 | + def test_superuser_allowed(self): |
| 333 | + """ |
| 334 | + Superusers should always be allowed through legacy fallback. |
| 335 | + """ |
| 336 | + superuser = UserFactory(is_superuser=True) |
| 337 | + |
| 338 | + client = APIClient() |
| 339 | + client.force_authenticate(user=superuser) |
| 340 | + |
| 341 | + resp = client.get(self.get_url(self.course_key)) |
| 342 | + |
| 343 | + self.assertEqual(resp.status_code, status.HTTP_200_OK) |
| 344 | + |
| 345 | + def test_non_staff_user_cannot_access(self): |
| 346 | + """ |
| 347 | + User without permissions should be denied. |
| 348 | + This case validates that a non-staff user cannot access even |
| 349 | + if they have course author access to the course. |
| 350 | + """ |
| 351 | + non_staff_user = UserFactory() |
| 352 | + non_staff_client = APIClient() |
| 353 | + self.add_user_to_role(non_staff_user, COURSE_DATA_RESEARCHER.external_key) |
| 354 | + non_staff_client.force_authenticate(user=non_staff_user) |
| 355 | + |
| 356 | + resp = non_staff_client.get(self.get_url(self.course_key)) |
| 357 | + self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN) |
0 commit comments