Skip to content

Commit 3fd8b63

Browse files
refactor: consider no filtering as first version
1 parent bcc4629 commit 3fd8b63

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

openedx_authz/api/decorators.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Decorators for the authorization public API."""
22
from functools import wraps
33

4+
from django.conf import settings
5+
46
from openedx_authz.api.data import ScopeData
57
from openedx_authz.engine.enforcer import enforcer
68
from openedx_authz.engine.filter import Filter
@@ -35,8 +37,18 @@ def get_all_roles():
3537
def get_roles_in_scope(scope: ScopeData):
3638
return enforcer.get_filtered_roles(scope.namespaced_key)
3739
"""
38-
FILTER_DATA_CLASSES = {
40+
FILTER_DATA_CLASSES = { # Consider empty for no filtering
3941
"scope": ScopeData,
42+
# TODO: currently ALLOW_FILTERED_POLICY_LOADING is False to avoid partial policy loads. We can
43+
# Allow filtering on scope (initially) once we have a CONF model that supports this so filtering is meaningful,
44+
# consistent and doesn't lead to partial policy loads.
45+
# We can consider this modeling to avoid partial loads and inconsistent states:
46+
# 1. g only for user-role-scope bindings
47+
# 2. p only for permission-role bindings
48+
# 3. g2 only for role-role bindings
49+
# 4. g3 only for permission grouping
50+
# This way for a user we'd only need to load g ( filter only for the scope or user) , p, g2, g3 policies in each request
51+
# The only filter binding would be g, the rest loads entirely to avoid not loading definitions.
4052
}
4153

4254
def build_filter_from_args(args) -> Filter:
@@ -48,6 +60,10 @@ def build_filter_from_args(args) -> Filter:
4860
Returns:
4961
Filter: A Filter object populated with relevant filter values.
5062
"""
63+
# Fallback to no filtering in case of misbehavior
64+
if settings.ALLOW_FILTERED_POLICY_LOADING is False:
65+
return Filter()
66+
5167
filter_obj = Filter()
5268
if not filter_on or filter_on not in FILTER_DATA_CLASSES:
5369
return filter_obj

openedx_authz/settings/common.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ def plugin_settings(settings):
3030
# Redis host and port are temporarily loaded here for the MVP
3131
settings.REDIS_HOST = "redis"
3232
settings.REDIS_PORT = 6379
33+
34+
# TODO: Set to True when we have a CONF model that supports filtered policy loading meaningfully
35+
# to avoid partial policy loads and inconsistent states.
36+
# See comments in api/decorators.py for more details.
37+
settings.ALLOW_FILTERED_POLICY_LOADING = False

openedx_authz/tests/api/test_decorators.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,3 +375,51 @@ def test_decorator_with_different_scopes(
375375
)
376376
else:
377377
self.assertEqual(len(permissions), 0)
378+
379+
def test_decorator_with_permission_grouping(self):
380+
"""Test decorator behavior with permission grouping in policies.
381+
382+
For example:
383+
- manage_library_team includes view_library_team through g2
384+
385+
This test verifies that when a user has edit permissions, they also implicitly have
386+
delete permissions due to the permission grouping defined in the policy.
387+
388+
Expected result:
389+
- Decorator loads filtered policies for the given scope
390+
- User with manage_library_team role can also view_library_team
391+
- Enforcer is cleared after execution
392+
"""
393+
scope = ScopeData(external_key="lib:Org1:math_101")
394+
subject = SubjectData(external_key="alice")
395+
396+
@manage_policy_lifecycle(filter_on="scope")
397+
def check_grouped_permissions(scope_arg, subject_arg):
398+
"""Check if subject has grouped permissions in the given scope.
399+
400+
Expected scenario:
401+
- Alice has library_admin role in lib:Org1:math_101
402+
- library_admin role includes manage_library_team
403+
- manage_library_team includes view_library_team through g2
404+
"""
405+
can_manage_team = global_enforcer.enforce(
406+
subject_arg.namespaced_key,
407+
ActionData(external_key="manage_library_team").namespaced_key,
408+
scope_arg.namespaced_key,
409+
)
410+
411+
can_view_team = global_enforcer.enforce(
412+
subject_arg.namespaced_key,
413+
ActionData(external_key="view_library_team").namespaced_key,
414+
scope_arg.namespaced_key,
415+
)
416+
417+
return {
418+
"can_manage_team": can_manage_team,
419+
"can_view_team": can_view_team,
420+
}
421+
422+
result = check_grouped_permissions(scope, subject)
423+
424+
self.assertTrue(result["can_manage_team"], "Alice should be able to manage library team")
425+
self.assertTrue(result["can_view_team"], "Alice should be able to view library team due to grouping")

0 commit comments

Comments
 (0)