Skip to content

Commit 9ceaa07

Browse files
refactor: initialize enforcer when firstly use to avoid raising errors while initializing application
1 parent 4a77050 commit 9ceaa07

8 files changed

Lines changed: 72 additions & 52 deletions

File tree

openedx_authz/api/permissions.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
from openedx_authz.api.data import ActionData, PermissionData, PolicyIndex, ScopeData, SubjectData
99
from openedx_authz.engine.enforcer import AuthzEnforcer
1010

11-
enforcer = AuthzEnforcer.get_enforcer()
12-
1311
__all__ = [
1412
"get_permission_from_policy",
1513
"get_all_permissions_in_scope",
@@ -44,7 +42,9 @@ def get_all_permissions_in_scope(scope: ScopeData) -> list[PermissionData]:
4442
Returns:
4543
list of PermissionData: A list of PermissionData objects associated with the given scope.
4644
"""
47-
actions = enforcer.get_filtered_policy(PolicyIndex.SCOPE.value, scope.namespaced_key)
45+
actions = AuthzEnforcer.get_enforcer().get_filtered_policy(
46+
PolicyIndex.SCOPE.value, scope.namespaced_key
47+
)
4848
return [get_permission_from_policy(action) for action in actions]
4949

5050

@@ -63,5 +63,6 @@ def is_subject_allowed(
6363
Returns:
6464
bool: True if the subject has the specified permission in the scope, False otherwise.
6565
"""
66-
enforcer.load_policy()
67-
return enforcer.enforce(subject.namespaced_key, action.namespaced_key, scope.namespaced_key)
66+
return AuthzEnforcer.get_enforcer().enforce(
67+
subject.namespaced_key, action.namespaced_key, scope.namespaced_key
68+
)

openedx_authz/api/roles.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
from openedx_authz.api.permissions import get_permission_from_policy
2323
from openedx_authz.engine.enforcer import AuthzEnforcer
2424

25-
enforcer = AuthzEnforcer.get_enforcer()
2625

2726
__all__ = [
2827
"get_permissions_for_single_role",
@@ -61,7 +60,7 @@ def get_permissions_for_single_role(
6160
Returns:
6261
list[PermissionData]: A list of PermissionData objects associated with the given role.
6362
"""
64-
policies = enforcer.get_implicit_permissions_for_user(role.namespaced_key)
63+
policies = AuthzEnforcer.get_enforcer().get_implicit_permissions_for_user(role.namespaced_key)
6564
return [get_permission_from_policy(policy) for policy in policies]
6665

6766

@@ -116,8 +115,7 @@ def get_permissions_for_active_roles_in_scope(
116115
dict[str, list[PermissionData]]: A dictionary mapping the role external_key to its
117116
permissions and scopes.
118117
"""
119-
enforcer.load_policy()
120-
filtered_policy = enforcer.get_filtered_grouping_policy(
118+
filtered_policy = AuthzEnforcer.get_enforcer().get_filtered_grouping_policy(
121119
GroupingPolicyIndex.SCOPE.value, scope.namespaced_key
122120
)
123121

@@ -148,8 +146,7 @@ def get_role_definitions_in_scope(scope: ScopeData) -> list[RoleData]:
148146
Returns:
149147
list[Role]: A list of roles.
150148
"""
151-
enforcer.load_policy()
152-
policy_filtered = enforcer.get_filtered_policy(
149+
policy_filtered = AuthzEnforcer.get_enforcer().get_filtered_policy(
153150
PolicyIndex.SCOPE.value, scope.namespaced_key
154151
)
155152

@@ -182,7 +179,7 @@ def get_all_roles_names() -> list[str]:
182179
Returns:
183180
list[str]: A list of role names.
184181
"""
185-
return enforcer.get_all_subjects()
182+
return AuthzEnforcer.get_enforcer().get_all_subjects()
186183

187184

188185
def get_all_roles_in_scope(scope: ScopeData) -> list[list[str]]:
@@ -194,8 +191,7 @@ def get_all_roles_in_scope(scope: ScopeData) -> list[list[str]]:
194191
Returns:
195192
list[list[str]]: A list of policies in the specified scope.
196193
"""
197-
enforcer.load_policy()
198-
return enforcer.get_filtered_grouping_policy(
194+
return AuthzEnforcer.get_enforcer().get_filtered_grouping_policy(
199195
GroupingPolicyIndex.SCOPE.value, scope.namespaced_key
200196
)
201197

@@ -213,8 +209,7 @@ def assign_role_to_subject_in_scope(
213209
Returns:
214210
bool: True if the role was assigned successfully, False otherwise.
215211
"""
216-
enforcer.load_policy()
217-
return enforcer.add_role_for_user_in_domain(
212+
AuthzEnforcer.get_enforcer().add_role_for_user_in_domain(
218213
subject.namespaced_key,
219214
role.namespaced_key,
220215
scope.namespaced_key,
@@ -247,8 +242,7 @@ def unassign_role_from_subject_in_scope(
247242
Returns:
248243
bool: True if the role was unassigned successfully, False otherwise.
249244
"""
250-
enforcer.load_policy()
251-
return enforcer.delete_roles_for_user_in_domain(
245+
AuthzEnforcer.get_enforcer().delete_roles_for_user_in_domain(
252246
subject.namespaced_key, role.namespaced_key, scope.namespaced_key
253247
)
254248

@@ -277,7 +271,7 @@ def get_subject_role_assignments(subject: SubjectData) -> list[RoleAssignmentDat
277271
list[RoleAssignmentData]: A list of role assignments for the subject.
278272
"""
279273
role_assignments = []
280-
for policy in enforcer.get_filtered_grouping_policy(
274+
for policy in AuthzEnforcer.get_enforcer().get_filtered_grouping_policy(
281275
GroupingPolicyIndex.SUBJECT.value, subject.namespaced_key
282276
):
283277
role = RoleData(namespaced_key=policy[GroupingPolicyIndex.ROLE.value])
@@ -305,10 +299,10 @@ def get_subject_role_assignments_in_scope(
305299
Returns:
306300
list[RoleAssignmentData]: A list of role assignments for the subject in the scope.
307301
"""
308-
enforcer.load_policy()
302+
AuthzEnforcer.get_enforcer().load_policy()
309303
# TODO: we still need to get the remaining data for the role like email, etc
310304
role_assignments = []
311-
for namespaced_key in enforcer.get_roles_for_user_in_domain(
305+
for namespaced_key in AuthzEnforcer.get_enforcer().get_roles_for_user_in_domain(
312306
subject.namespaced_key, scope.namespaced_key
313307
):
314308
role = RoleData(namespaced_key=namespaced_key)
@@ -340,7 +334,7 @@ def get_subject_role_assignments_for_role_in_scope(
340334
list[RoleAssignmentData]: A list of subjects assigned to the specified role in the specified scope.
341335
"""
342336
role_assignments = []
343-
for subject in enforcer.get_users_for_role_in_domain(
337+
for subject in AuthzEnforcer.get_enforcer().get_users_for_role_in_domain(
344338
role.namespaced_key, scope.namespaced_key
345339
):
346340
if subject.startswith(f"{RoleData.NAMESPACE}{RoleData.SEPARATOR}"):
@@ -404,6 +398,8 @@ def get_subjects_for_role(role: RoleData) -> list[SubjectData]:
404398
Returns:
405399
list[SubjectData]: A list of subjects assigned to the specified role.
406400
"""
407-
enforcer.load_policy()
408-
policies = enforcer.get_filtered_grouping_policy(GroupingPolicyIndex.ROLE.value, role.namespaced_key)
401+
policies = AuthzEnforcer.get_enforcer().get_filtered_grouping_policy(
402+
GroupingPolicyIndex.ROLE.value,
403+
role.namespaced_key
404+
)
409405
return [SubjectData(namespaced_key=policy[GroupingPolicyIndex.SUBJECT.value]) for policy in policies]

openedx_authz/apps.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ class OpenedxAuthzConfig(AppConfig):
4343

4444
def ready(self):
4545
"""Initialization layer for the openedx_authz app."""
46-
# Initialize the enforcer to ensure it's ready when the app starts
47-
try:
48-
from openedx_authz.engine.enforcer import AuthzEnforcer
49-
AuthzEnforcer()
50-
except ImproperlyConfigured:
51-
# The app might not be fully configured yet (e.g., during migrations).
52-
# In such cases, we skip the enforcer initialization.
53-
pass
46+
# DO NOT initialize the enforcer here to avoid issues when
47+
# apps are not fully loaded (e.g., while pulling translations).
48+
# It's best to lazy load the enforcer when needed it's first used.
49+
pass

openedx_authz/engine/apps.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@ class CasbinAdapterConfig(AppConfig):
88

99
def ready(self):
1010
"""Initialization layer for the casbin_adapter app."""
11-
12-
try:
13-
from casbin_adapter.enforcer import initialize_enforcer
14-
15-
db_alias = getattr(settings, "CASBIN_DB_ALIAS", "default")
16-
initialize_enforcer(db_alias)
17-
except ImproperlyConfigured:
18-
# The app might not be fully configured yet (e.g., during migrations).
19-
# In such cases, we skip the enforcer initialization.
20-
pass
11+
# DO NOT initialize the enforcer here to avoid issues when
12+
# apps are not fully loaded (e.g., while pulling translations).
13+
# It's best to lazy load the enforcer when needed it's first used.
14+
pass

openedx_authz/engine/enforcer.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from openedx_authz.engine.adapter import ExtendedAdapter
2525
from openedx_authz.engine.watcher import Watcher
26+
from casbin_adapter.enforcer import initialize_enforcer
2627

2728
logger = logging.getLogger(__name__)
2829

@@ -33,9 +34,17 @@ class AuthzEnforcer:
3334
Ensures a single enforcer instance is created safely and configured with the
3435
ExtendedAdapter and Redis watcher for policy management and synchronization.
3536
36-
Usage:
37+
There are two main use cases for this class:
38+
1. Directly get the enforcer instance and initialize it if needed:
39+
from openedx_authz.engine.enforcer import AuthzEnforcer
3740
enforcer = AuthzEnforcer.get_enforcer()
3841
allowed = enforcer.enforce(user, resource, action)
42+
2. Instantiate the class to get the singleton enforcer instance:
43+
from openedx_authz.engine.enforcer import AuthzEnforcer
44+
enforcer = AuthzEnforcer()
45+
allowed = enforcer.get_enforcer().enforce(user, resource, action)
46+
47+
Any of the two approaches will yield the same singleton enforcer instance.
3948
"""
4049

4150
_enforcer = None
@@ -62,13 +71,16 @@ def _initialize_enforcer() -> FastEnforcer:
6271
"""
6372
Create and configure the Casbin FastEnforcer instance.
6473
65-
This function initializes the Casbin FastEnforcer with the ExtendedAdapter
66-
for database-backed policy storage and sets up the Redis watcher for
67-
real-time policy synchronization.
74+
This method initializes the FastEnforcer with the ExtendedAdapter
75+
for database policy storage and sets up the Redis watcher for real-time
76+
policy synchronization if the Watcher is available. It also initializes
77+
the enforcer with the specified database alias from settings.
6878
6979
Returns:
7080
FastEnforcer: Configured Casbin enforcer with adapter and watcher
7181
"""
82+
db_alias = getattr(settings, "CASBIN_DB_ALIAS", "default")
83+
initialize_enforcer(db_alias)
7284
adapter = ExtendedAdapter()
7385
enforcer = FastEnforcer(settings.CASBIN_MODEL, adapter, enable_log=True)
7486
enforcer.enable_auto_save(True)

openedx_authz/management/commands/load_policies.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from openedx_authz.engine.enforcer import AuthzEnforcer
1616
from openedx_authz.engine.utils import migrate_policy_between_enforcers
1717

18-
global_enforcer = AuthzEnforcer.get_enforcer()
1918

2019

2120
class Command(BaseCommand):
@@ -76,7 +75,7 @@ def handle(self, *args, **options):
7675
)
7776

7877
source_enforcer = casbin.Enforcer(model_file_path, policy_file_path)
79-
self.migrate_policies(source_enforcer, global_enforcer)
78+
self.migrate_policies(source_enforcer, AuthzEnforcer.get_enforcer())
8079

8180
def migrate_policies(self, source_enforcer, target_enforcer):
8281
"""Migrate policies from the source enforcer to the target enforcer.

openedx_authz/tests/api/test_roles.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from openedx_authz.engine.enforcer import AuthzEnforcer
3535
from openedx_authz.engine.utils import migrate_policy_between_enforcers
3636

37-
global_enforcer = AuthzEnforcer.get_enforcer()
3837

3938

4039
class BaseRolesTestCase(TestCase):
@@ -52,6 +51,7 @@ def _seed_database_with_policies(cls):
5251
This simulates the one-time database seeding that would happen
5352
during application deployment, separate from the runtime policy loading.
5453
"""
54+
global_enforcer = AuthzEnforcer.get_enforcer()
5555
global_enforcer.load_policy()
5656
migrate_policy_between_enforcers(
5757
source_enforcer=casbin.Enforcer(
@@ -243,6 +243,16 @@ def setUpClass(cls):
243243
]
244244
cls._assign_roles_to_users(assignments=assignments)
245245

246+
def setUp(self):
247+
"""Set up test environment."""
248+
super().setUp()
249+
AuthzEnforcer.get_enforcer().load_policy() # Load policies before each test to simulate fresh start
250+
251+
def tearDown(self):
252+
"""Clean up after each test to ensure isolation."""
253+
super().tearDown()
254+
AuthzEnforcer.get_enforcer().clear_policy() # Clear policies after each test to ensure isolation
255+
246256

247257
@ddt
248258
class TestRolesAPI(RolesTestSetupMixin):

0 commit comments

Comments
 (0)