|
13 | 13 | from django.test import override_settings |
14 | 14 | from django.test.client import RequestFactory |
15 | 15 | from django.urls import reverse |
| 16 | +from edx_toggles.toggles.testutils import override_waffle_flag |
16 | 17 | from opaque_keys.edx.keys import CourseKey |
17 | 18 | from opaque_keys.edx.locator import CourseLocator |
18 | | -from openedx_authz.constants.roles import COURSE_EDITOR |
19 | 19 | from organizations.api import add_organization, get_course_organizations, get_organization_by_short_name |
20 | 20 | from organizations.exceptions import InvalidOrganizationException |
21 | 21 | from organizations.models import Organization |
|
34 | 34 | from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole, OrgContentCreatorRole |
35 | 35 | from common.djangoapps.student.tests.factories import AdminFactory, UserFactory |
36 | 36 | from openedx.core.djangoapps.authz.tests.mixins import CourseAuthoringAuthzTestMixin |
| 37 | +from openedx.core.toggles import AUTHZ_COURSE_AUTHORING_FLAG |
37 | 38 | from xmodule.course_block import CourseFields |
38 | 39 | from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase |
39 | 40 | from xmodule.modulestore.tests.factories import CourseFactory |
@@ -386,119 +387,37 @@ def test_default_enable_flexible_peer_openassessments_on_rerun( |
386 | 387 | ) |
387 | 388 |
|
388 | 389 |
|
389 | | -class TestCourseHandlerAuthz( |
390 | | - CourseAuthoringAuthzTestMixin, |
391 | | - ModuleStoreTestCase, |
392 | | -): |
| 390 | +@ddt.ddt |
| 391 | +class TestCourseHandlerStaffAccess(ModuleStoreTestCase): |
393 | 392 | """ |
394 | | - AuthZ integration tests for course_handler using real RBAC (no mocks). |
| 393 | + Tests that global staff can create a course through course_handler with no |
| 394 | + course-specific role, covering the GlobalStaff bypass in user_has_role. Course |
| 395 | + creation doesn't check AuthZ (see ADR 0027), so this doesn't use the AuthZ mixin. |
395 | 396 | """ |
396 | 397 |
|
397 | 398 | def setUp(self): |
398 | 399 | super().setUp() |
399 | | - |
400 | 400 | self.url = reverse("course_handler") |
| 401 | + self.staff_user = AdminFactory() |
| 402 | + self.staff_client = AjaxEnabledTestClient() |
| 403 | + self.staff_client.login(username=self.staff_user.username, password=self.TEST_PASSWORD) |
401 | 404 |
|
402 | | - # Create a base course to extract org |
403 | | - self.course = CourseFactory.create() |
404 | | - self.course_key = self.course.id |
405 | | - self.org = self.course_key.org |
406 | | - |
407 | | - # If your policy expects this format, keep it |
408 | | - self.org_key = f"course-v1:{self.org}+*" |
409 | | - |
410 | | - self.authorized_client = AjaxEnabledTestClient() |
411 | | - self.authorized_client.login( |
412 | | - username=self.authorized_user.username, |
413 | | - password=self.password, |
414 | | - ) |
415 | | - |
416 | | - self.unauthorized_client = AjaxEnabledTestClient() |
417 | | - self.unauthorized_client.login( |
418 | | - username=self.unauthorized_user.username, |
419 | | - password=self.password, |
420 | | - ) |
421 | | - self.authorized_staff_client = AjaxEnabledTestClient() |
422 | | - self.authorized_staff_client.login( |
423 | | - username=self.staff_user.username, |
424 | | - password=self.password, |
425 | | - ) |
426 | | - |
427 | | - # ------------------------------------------------------------ |
428 | | - # CREATE COURSE -- Non-staff users and existing Organization |
429 | | - # ------------------------------------------------------------ |
430 | | - @override_settings(DISABLE_COURSE_CREATION=False) |
431 | | - def test_create_course_unauthorized(self): |
432 | | - """ |
433 | | - User without role cannot create course. |
434 | | - """ |
435 | | - |
436 | | - response = self.unauthorized_client.ajax_post(self.url, { |
437 | | - "org": self.org, |
438 | | - "number": "CS101", |
439 | | - "display_name": "Authz Course", |
440 | | - "run": "2026_T1", |
441 | | - }) |
442 | | - |
443 | | - assert response.status_code == 403 |
444 | | - |
445 | | - @override_settings(DISABLE_COURSE_CREATION=False) |
446 | | - def test_create_course_unauthorized_with_role(self): |
447 | | - """ |
448 | | - User with role but without required permission cannot create course. |
449 | | - """ |
450 | | - |
451 | | - self.add_user_to_role_in_course( |
452 | | - self.unauthorized_user, |
453 | | - COURSE_EDITOR.external_key, |
454 | | - "course-v1:someotherorg+*", |
455 | | - ) |
456 | | - |
457 | | - response = self.unauthorized_client.ajax_post(self.url, { |
458 | | - "org": self.org, |
459 | | - "number": "CS101", |
460 | | - "display_name": "Authz Course", |
461 | | - "run": "2026_T1", |
462 | | - }) |
463 | | - |
464 | | - assert response.status_code == 403 |
465 | | - |
466 | | - # ------------------------------------------------------------ |
467 | | - # CREATE COURSE -- Staff users |
468 | | - # Only staff users can create course, and they can do it |
469 | | - # without an org role. |
470 | | - # ------------------------------------------------------------ |
471 | | - def test_create_course_staff(self): |
| 405 | + @ddt.data(True, False) |
| 406 | + def test_create_course_staff(self, authz_enabled): |
472 | 407 | """ |
473 | | - Staff user can create course. |
| 408 | + Staff user can create a course with no prior course-specific role, whether |
| 409 | + the AuthZ course-authoring flag is on or off. |
474 | 410 | """ |
475 | | - response = self.authorized_staff_client.ajax_post(self.url, { |
476 | | - "org": self.org, |
477 | | - "number": "CS101", |
478 | | - "display_name": "Authz Course", |
479 | | - "run": "2026_T1", |
480 | | - }) |
| 411 | + with override_waffle_flag(AUTHZ_COURSE_AUTHORING_FLAG, active=authz_enabled): |
| 412 | + response = self.staff_client.ajax_post(self.url, { |
| 413 | + "org": "StaffOrg", |
| 414 | + "number": "CS101", |
| 415 | + "display_name": "Staff Course", |
| 416 | + "run": "2026_T1", |
| 417 | + }) |
481 | 418 |
|
482 | 419 | assert response.status_code == 200 |
483 | 420 |
|
484 | | - # ------------------------------------------------------------ |
485 | | - # FEATURE FLAG |
486 | | - # ------------------------------------------------------------ |
487 | | - @override_settings(DISABLE_COURSE_CREATION=True) |
488 | | - def test_create_course_disabled_by_flag(self): |
489 | | - """ |
490 | | - Even authorized users cannot create course if feature flag is off. |
491 | | - """ |
492 | | - |
493 | | - response = self.authorized_staff_client.ajax_post(self.url, { |
494 | | - "org": self.org, |
495 | | - "number": "CS101", |
496 | | - "display_name": "Authz Course", |
497 | | - "run": "2026_T1", |
498 | | - }) |
499 | | - |
500 | | - assert response.status_code == 403 |
501 | | - |
502 | 421 |
|
503 | 422 | class TestCourseRerunAuthz( |
504 | 423 | CourseAuthoringAuthzTestMixin, |
|
0 commit comments