|
11 | 11 |
|
12 | 12 | import casbin |
13 | 13 | import pytest |
| 14 | +from casbin.util import key_match_func |
14 | 15 | from ddt import data, ddt, unpack |
15 | 16 | from django.contrib.auth import get_user_model |
16 | 17 |
|
17 | 18 | from openedx_authz import ROOT_DIRECTORY |
18 | | -from openedx_authz.api.data import GLOBAL_SCOPE_WILDCARD |
19 | | -from openedx_authz.constants import roles |
| 19 | +from openedx_authz.api.data import GLOBAL_SCOPE_WILDCARD, ContentLibraryData, CourseOverviewData |
| 20 | +from openedx_authz.constants import permissions, roles |
20 | 21 | from openedx_authz.engine.matcher import is_admin_or_superuser_check |
21 | 22 | from openedx_authz.tests.test_utils import ( |
22 | 23 | make_action_key, |
| 24 | + make_course_key, |
23 | 25 | make_library_key, |
24 | 26 | make_role_key, |
25 | 27 | make_scope_key, |
26 | 28 | make_user_key, |
| 29 | + make_wildcard_key, |
27 | 30 | ) |
28 | 31 |
|
29 | 32 | User = get_user_model() |
@@ -73,6 +76,7 @@ def setUpClass(cls) -> None: |
73 | 76 |
|
74 | 77 | cls.enforcer = casbin.Enforcer(model_file) |
75 | 78 | cls.enforcer.add_function("is_staff_or_superuser", is_admin_or_superuser_check) |
| 79 | + cls.enforcer.add_named_domain_matching_func("g", key_match_func) |
76 | 80 |
|
77 | 81 | def _load_policy(self, policy: list[str]) -> None: |
78 | 82 | """ |
@@ -583,6 +587,82 @@ def test_wildcard_library_access(self, scope: str, expected_result: bool): |
583 | 587 | self._test_enforcement(self.POLICY, request) |
584 | 588 |
|
585 | 589 |
|
| 590 | +@ddt |
| 591 | +class OrgGlobEnforcementTests(CasbinEnforcementTestCase): |
| 592 | + """ |
| 593 | + Tests for organization-level glob patterns in course and library scopes. |
| 594 | +
|
| 595 | + This test class verifies that policies defined with org-level glob patterns |
| 596 | + (e.g., "course-v1:OpenedX*" or "lib:DemoX*") are correctly enforced for |
| 597 | + concrete course and library scopes that belong to those organizations. |
| 598 | + """ |
| 599 | + |
| 600 | + POLICY = [ |
| 601 | + # Policies |
| 602 | + [ |
| 603 | + "p", |
| 604 | + make_role_key(roles.COURSE_STAFF.external_key), |
| 605 | + make_action_key("courses.view_course"), |
| 606 | + make_wildcard_key(CourseOverviewData.NAMESPACE), |
| 607 | + "allow", |
| 608 | + ], |
| 609 | + [ |
| 610 | + "p", |
| 611 | + make_role_key(roles.LIBRARY_ADMIN.external_key), |
| 612 | + make_action_key("content_libraries.view_library"), |
| 613 | + make_wildcard_key(ContentLibraryData.NAMESPACE), |
| 614 | + "allow", |
| 615 | + ], |
| 616 | + # Role assignments |
| 617 | + [ |
| 618 | + "g", |
| 619 | + make_user_key("user-1"), |
| 620 | + make_role_key(roles.COURSE_STAFF.external_key), |
| 621 | + make_course_key("course-v1:OpenedX*"), |
| 622 | + ], |
| 623 | + [ |
| 624 | + "g", |
| 625 | + make_user_key("user-2"), |
| 626 | + make_role_key(roles.LIBRARY_ADMIN.external_key), |
| 627 | + make_library_key("lib:DemoX*"), |
| 628 | + ], |
| 629 | + ] |
| 630 | + |
| 631 | + CASES = [ |
| 632 | + # Permission granted |
| 633 | + { |
| 634 | + "subject": make_user_key("user-1"), |
| 635 | + "action": make_action_key(permissions.COURSES_VIEW_COURSE.action.external_key), |
| 636 | + "scope": make_course_key("course-v1:OpenedX+DemoCourse+2026_T1"), |
| 637 | + "expected_result": True, |
| 638 | + }, |
| 639 | + { |
| 640 | + "subject": make_user_key("user-2"), |
| 641 | + "action": make_action_key(permissions.VIEW_LIBRARY.action.external_key), |
| 642 | + "scope": make_library_key("lib:DemoX:OrgLevelGlobLib"), |
| 643 | + "expected_result": True, |
| 644 | + }, |
| 645 | + # Permission denied |
| 646 | + { |
| 647 | + "subject": make_user_key("user-1"), |
| 648 | + "action": make_action_key(permissions.COURSES_VIEW_COURSE.action.external_key), |
| 649 | + "scope": make_course_key("course-v1:InexistentOrg+DemoCourse+2026_T1"), |
| 650 | + "expected_result": False, |
| 651 | + }, |
| 652 | + { |
| 653 | + "subject": make_user_key("user-2"), |
| 654 | + "action": make_action_key(permissions.VIEW_LIBRARY.action.external_key), |
| 655 | + "scope": make_library_key("lib:InexistentOrg:OrgLevelGlobLib"), |
| 656 | + "expected_result": False, |
| 657 | + }, |
| 658 | + ] |
| 659 | + |
| 660 | + @data(*CASES) |
| 661 | + def test_org_level_glob_enforcement(self, request: AuthRequest): |
| 662 | + """Test that org-level glob patterns in scopes are enforced correctly.""" |
| 663 | + self._test_enforcement(self.POLICY, request) |
| 664 | + |
| 665 | + |
586 | 666 | @pytest.mark.django_db |
587 | 667 | @ddt |
588 | 668 | class StaffSuperuserAccessTests(CasbinEnforcementTestCase): |
|
0 commit comments