11"""
2- Tests for Casbin enforcement using model.conf and authz.policy files .
2+ Comprehensive test suite for Open edX authorization enforcement using Casbin .
33
4- This module contains comprehensive tests for the authorization enforcement
5- using Casbin with the configured model and policy files .
4+ This module validates the authorization system implemented with Casbin, testing
5+ various aspects of the permission model.
66"""
77
88import os
1212import casbin
1313from ddt import data , ddt , unpack
1414
15+ from openedx_authz import ROOT_DIRECTORY
16+
1517
1618class AuthRequest (TypedDict ):
1719 """
@@ -43,27 +45,41 @@ class CasbinEnforcementTestCase(TestCase):
4345 """
4446 Test case for Casbin enforcement policies.
4547
46- This test class loads the model.conf and authz.policy files and runs
48+ This test class loads the model.conf and the provided policies and runs
4749 enforcement tests for different user roles and permissions.
4850 """
4951
5052 @classmethod
5153 def setUpClass (cls ) -> None :
52- """Set up the Casbin enforcer with model and policy files ."""
54+ """Set up the Casbin enforcer."""
5355 super ().setUpClass ()
5456
55- engine_config_dir = os .path .join (os . path . dirname ( os . path . dirname ( __file__ )) , "engine" , "config" )
57+ engine_config_dir = os .path .join (ROOT_DIRECTORY , "engine" , "config" )
5658 model_file = os .path .join (engine_config_dir , "model.conf" )
5759
5860 if not os .path .isfile (model_file ):
5961 raise FileNotFoundError (f"Model file not found: { model_file } " )
6062
6163 cls .enforcer = casbin .Enforcer (model_file )
6264
63- def _load_policy (self , policy : list [str ] = None ) -> None :
64- """Load policy into the enforcer."""
65+ def _load_policy (self , policy : list [str ]) -> None :
66+ """
67+ Load policy rules into the Casbin enforcer.
68+
69+ This method clears any existing policies and loads the provided policy rules
70+ into the appropriate policy stores (p for policies, g for role assignments,
71+ g2 for action groupings).
72+
73+ Args:
74+ policy (list[str]): List of policy rules where each rule is a
75+ list starting with the rule type ('p', 'g', or 'g2') followed by
76+ the rule parameters.
77+
78+ Raises:
79+ ValueError: If a policy rule has an invalid type (not 'p', 'g', or 'g2').
80+ """
6581 self .enforcer .clear_policy ()
66- for rule in policy or [] :
82+ for rule in policy :
6783 if rule [0 ] == "p" :
6884 self .enforcer .add_named_policy ("p" , rule [1 :])
6985 elif rule [0 ] == "g" :
@@ -73,7 +89,7 @@ def _load_policy(self, policy: list[str] = None) -> None:
7389 else :
7490 raise ValueError (f"Invalid policy rule: { rule } " )
7591
76- def _test_enforcement (self , policy : list [str ] = None , request : AuthRequest = None ) -> None :
92+ def _test_enforcement (self , policy : list [str ], request : AuthRequest ) -> None :
7793 """
7894 Helper method to test enforcement and provide detailed feedback.
7995
@@ -90,11 +106,18 @@ def _test_enforcement(self, policy: list[str] = None, request: AuthRequest = Non
90106
91107@ddt
92108class SystemWideRoleTests (CasbinEnforcementTestCase ):
93- """Tests for system-wide roles with global access permissions."""
109+ """
110+ Tests for system-wide roles with global access permissions.
111+
112+ This test class verifies that users assigned to system-wide roles (with global scope "*")
113+ can access resources across all scopes and namespaces. Platform administrators should
114+ have unrestricted access to manage any resource in the system, regardless of the
115+ specific scope (organization, course, library, etc.).
116+ """
94117
95118 POLICY = [
96119 ["p" , "role:platform_admin" , "act:manage" , "*" , "allow" ],
97- ["g" , "user:user-1" , "role:platform_admin" ],
120+ ["g" , "user:user-1" , "role:platform_admin" , "*" ],
98121 ] + COMMON_ACTION_GROUPING
99122
100123 GENERAL_CASES = [
@@ -132,11 +155,17 @@ def test_platform_admin_general_access(self, request: AuthRequest):
132155
133156@ddt
134157class ActionGroupingTests (CasbinEnforcementTestCase ):
135- """Tests for action grouping."""
158+ """
159+ Tests for action grouping and permission inheritance.
160+
161+ This test class verifies that action grouping works correctly, where high-level
162+ actions (like 'manage') automatically grant access to lower-level actions
163+ (like 'edit', 'read', 'write', 'delete') through the g2 grouping mechanism.
164+ """
136165
137166 POLICY = [
138- ["p" , "role:role-1" , "act:manage" , "org:any-org " , "allow" ],
139- ["g" , "user:user-1" , "role:role-1" ],
167+ ["p" , "role:role-1" , "act:manage" , "org:* " , "allow" ],
168+ ["g" , "user:user-1" , "role:role-1" , "org:any-org" ],
140169 ] + COMMON_ACTION_GROUPING
141170
142171 CASES = [
@@ -174,29 +203,34 @@ def test_action_grouping_access(self, request: AuthRequest):
174203
175204@ddt
176205class RoleAssignmentTests (CasbinEnforcementTestCase ):
177- """Tests for role assignment."""
206+ """
207+ Tests for role assignment and scoped authorization.
208+
209+ This test class verifies that users with different roles can access resources
210+ within their assigned scopes.
211+ """
178212
179213 POLICY = [
180214 # Policies
181215 ["p" , "role:platform_admin" , "act:manage" , "*" , "allow" ],
182- ["p" , "role:org_admin" , "act:manage" , "org:any-org " , "allow" ],
183- ["p" , "role:org_editor" , "act:edit" , "org:any-org " , "allow" ],
184- ["p" , "role:org_author" , "act:write" , "org:any-org " , "allow" ],
185- ["p" , "role:course_admin" , "act:manage" , "course-v1:any-org+any-course+any-course-run " , "allow" ],
186- ["p" , "role:library_admin" , "act:manage" , "lib:any-library " , "allow" ],
187- ["p" , "role:library_editor" , "act:edit" , "lib:any-library " , "allow" ],
188- ["p" , "role:library_reviewer" , "act:read" , "lib:any-library " , "allow" ],
189- ["p" , "role:library_author" , "act:write" , "lib:any-library " , "allow" ],
216+ ["p" , "role:org_admin" , "act:manage" , "org:* " , "allow" ],
217+ ["p" , "role:org_editor" , "act:edit" , "org:* " , "allow" ],
218+ ["p" , "role:org_author" , "act:write" , "org:* " , "allow" ],
219+ ["p" , "role:course_admin" , "act:manage" , "course-v1:* " , "allow" ],
220+ ["p" , "role:library_admin" , "act:manage" , "lib:* " , "allow" ],
221+ ["p" , "role:library_editor" , "act:edit" , "lib:* " , "allow" ],
222+ ["p" , "role:library_reviewer" , "act:read" , "lib:* " , "allow" ],
223+ ["p" , "role:library_author" , "act:write" , "lib:* " , "allow" ],
190224 # Role assignments
191- ["g" , "user:user-1" , "role:platform_admin" ],
192- ["g" , "user:user-2" , "role:org_admin" ],
193- ["g" , "user:user-3" , "role:org_editor" ],
194- ["g" , "user:user-4" , "role:org_author" ],
195- ["g" , "user:user-5" , "role:course_admin" ],
196- ["g" , "user:user-6" , "role:library_admin" ],
197- ["g" , "user:user-7" , "role:library_editor" ],
198- ["g" , "user:user-8" , "role:library_reviewer" ],
199- ["g" , "user:user-9" , "role:library_author" ],
225+ ["g" , "user:user-1" , "role:platform_admin" , "*" ],
226+ ["g" , "user:user-2" , "role:org_admin" , "org:any-org" ],
227+ ["g" , "user:user-3" , "role:org_editor" , "org:any-org" ],
228+ ["g" , "user:user-4" , "role:org_author" , "org:any-org" ],
229+ ["g" , "user:user-5" , "role:course_admin" , "course-v1:any-org+any-course+any-course-run" ],
230+ ["g" , "user:user-6" , "role:library_admin" , "lib:any-library" ],
231+ ["g" , "user:user-7" , "role:library_editor" , "lib:any-library" ],
232+ ["g" , "user:user-8" , "role:library_reviewer" , "lib:any-library" ],
233+ ["g" , "user:user-9" , "role:library_author" , "lib:any-library" ],
200234 ] + COMMON_ACTION_GROUPING
201235
202236 CASES = [
@@ -264,12 +298,16 @@ def test_role_assignment_access(self, request: AuthRequest):
264298
265299@ddt
266300class DeniedAccessTests (CasbinEnforcementTestCase ):
267- """Tests for denied access."""
301+ """Tests for denied access scenarios.
302+
303+ This test class verifies that the authorization system correctly denies access
304+ when explicit deny rules override allow rules.
305+ """
268306
269307 POLICY = [
270308 ["p" , "role:platform_admin" , "act:manage" , "*" , "allow" ],
271309 ["p" , "role:platform_admin" , "act:manage" , "org:restricted-org" , "deny" ],
272- ["g" , "user:user-1" , "role:platform_admin" ],
310+ ["g" , "user:user-1" , "role:platform_admin" , "*" ],
273311 ] + COMMON_ACTION_GROUPING
274312
275313 CASES = [
@@ -319,7 +357,12 @@ def test_denied_access(self, request: AuthRequest):
319357
320358@ddt
321359class WildcardScopeTests (CasbinEnforcementTestCase ):
322- """Tests for wildcard scope."""
360+ """Tests for wildcard scope authorization patterns.
361+
362+ Verifies that users with roles assigned to wildcard scopes (like "*" for global access
363+ or "org:*" for organization-wide access) can properly access resources within their
364+ authorized scope boundaries.
365+ """
323366
324367 POLICY = [
325368 # Policies
@@ -328,10 +371,10 @@ class WildcardScopeTests(CasbinEnforcementTestCase):
328371 ["p" , "role:course_admin" , "act:manage" , "course-v1:*" , "allow" ],
329372 ["p" , "role:library_admin" , "act:manage" , "lib:*" , "allow" ],
330373 # Role assignments
331- ["g" , "user:user-1" , "role:platform_admin" ],
332- ["g" , "user:user-2" , "role:org_admin" ],
333- ["g" , "user:user-3" , "role:course_admin" ],
334- ["g" , "user:user-4" , "role:library_admin" ],
374+ ["g" , "user:user-1" , "role:platform_admin" , "*" ],
375+ ["g" , "user:user-2" , "role:org_admin" , "*" ],
376+ ["g" , "user:user-3" , "role:course_admin" , "*" ],
377+ ["g" , "user:user-4" , "role:library_admin" , "*" ],
335378 ] + COMMON_ACTION_GROUPING
336379
337380 @data (
0 commit comments