diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7a81b587..0804b100 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,9 +17,12 @@ Unreleased Added ===== +0.22.0 - 2026-02-19 +******************** + * ADR on the AuthZ for Course Authoring implementation plan. * ADR on the AuthZ for Course Authoring Feature Flag Implementation Details. - +* Defined courses roles and permissions mappings, including legacy compatible permissions. 0.21.0 - 2026-02-12 ******************** diff --git a/openedx_authz/__init__.py b/openedx_authz/__init__.py index ef2a05a7..de3fced5 100644 --- a/openedx_authz/__init__.py +++ b/openedx_authz/__init__.py @@ -4,6 +4,6 @@ import os -__version__ = "0.21.0" +__version__ = "0.22.0" ROOT_DIRECTORY = os.path.dirname(os.path.abspath(__file__)) diff --git a/openedx_authz/constants/permissions.py b/openedx_authz/constants/permissions.py index 0aa00e8e..f9064cc1 100644 --- a/openedx_authz/constants/permissions.py +++ b/openedx_authz/constants/permissions.py @@ -58,7 +58,177 @@ COURSES_NAMESPACE = "courses" -MANAGE_ADVANCED_SETTINGS = PermissionData( +COURSES_VIEW_COURSE = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_course"), + effect="allow", +) + +COURSES_CREATE_COURSE = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.create_course"), + effect="allow", +) + +COURSES_EDIT_COURSE_CONTENT = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.edit_course_content"), + effect="allow", +) + +COURSES_PUBLISH_COURSE_CONTENT = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.publish_course_content"), + effect="allow", +) + +COURSES_MANAGE_LIBRARY_UPDATES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_library_updates"), + effect="allow", +) + +COURSES_VIEW_COURSE_UPDATES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_course_updates"), + effect="allow", +) + +COURSES_MANAGE_COURSE_UPDATES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_course_updates"), + effect="allow", +) + +COURSES_VIEW_PAGES_AND_RESOURCES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_pages_and_resources"), + effect="allow", +) + +COURSES_MANAGE_PAGES_AND_RESOURCES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_pages_and_resources"), + effect="allow", +) + +COURSES_VIEW_FILES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_files"), + effect="allow", +) + +COURSES_CREATE_FILES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.create_files"), + effect="allow", +) + +COURSES_DELETE_FILES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.delete_files"), + effect="allow", +) + +COURSES_EDIT_FILES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.edit_files"), + effect="allow", +) + +COURSES_VIEW_SCHEDULE_AND_DETAILS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_schedule_and_details"), + effect="allow", +) + +COURSES_EDIT_SCHEDULE = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.edit_schedule"), + effect="allow", +) + +COURSES_EDIT_DETAILS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.edit_details"), + effect="allow", +) + +COURSES_VIEW_GRADING_SETTINGS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_grading_settings"), + effect="allow", +) + +COURSES_EDIT_GRADING_SETTINGS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.edit_grading_settings"), + effect="allow", +) + +COURSES_VIEW_COURSE_TEAM = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_course_team"), + effect="allow", +) + +COURSES_MANAGE_COURSE_TEAM = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_course_team"), + effect="allow", +) + +COURSES_MANAGE_GROUP_CONFIGURATIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_group_configurations"), + effect="allow", +) + +COURSES_MANAGE_ADVANCED_SETTINGS = PermissionData( action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_advanced_settings"), effect="allow", ) + +COURSES_MANAGE_CERTIFICATES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_certificates"), + effect="allow", +) + +COURSES_IMPORT_COURSE = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.import_course"), + effect="allow", +) + +COURSES_EXPORT_COURSE = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.export_course"), + effect="allow", +) + +COURSES_EXPORT_TAGS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.export_tags"), + effect="allow", +) + +COURSES_VIEW_CHECKLISTS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.view_checklists"), + effect="allow", +) + +COURSES_MANAGE_TAGS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_tags"), + effect="allow", +) + +COURSES_MANAGE_TAXONOMIES = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.manage_taxonomies"), + effect="allow", +) + +# Legacy Course permissions +# These permissions allow backwards compatibility with legacy code that depends on the old roles system +# These relate to legacy roles, if a openedx-authz role has one of these permissions, +# it will have the same permissions as the equivalent legacy roles on code that has not been updated to the new system. + +COURSES_LEGACY_INSTRUCTOR_ROLE_PERMISSIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.legacy_instructor_role_permissions"), + effect="allow", +) + +COURSES_LEGACY_STAFF_ROLE_PERMISSIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.legacy_staff_role_permissions"), + effect="allow", +) + +COURSES_LEGACY_LIMITED_STAFF_ROLE_PERMISSIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.legacy_limited_staff_role_permissions"), + effect="allow", +) + +COURSES_LEGACY_DATA_RESEARCHER_PERMISSIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.legacy_data_researcher_permissions"), + effect="allow", +) + +COURSES_LEGACY_BETA_TESTER_PERMISSIONS = PermissionData( + action=ActionData(external_key=f"{COURSES_NAMESPACE}.legacy_beta_tester_permissions"), + effect="allow", +) diff --git a/openedx_authz/constants/roles.py b/openedx_authz/constants/roles.py index ebbdb041..4ac6f941 100644 --- a/openedx_authz/constants/roles.py +++ b/openedx_authz/constants/roles.py @@ -60,9 +60,133 @@ # Course Roles and Permissions +COURSE_AUDITOR_PERMISSIONS = [ + permissions.COURSES_VIEW_COURSE, + permissions.COURSES_VIEW_COURSE_UPDATES, + permissions.COURSES_VIEW_PAGES_AND_RESOURCES, + permissions.COURSES_VIEW_FILES, + permissions.COURSES_VIEW_GRADING_SETTINGS, + permissions.COURSES_VIEW_CHECKLISTS, + permissions.COURSES_VIEW_COURSE_TEAM, + permissions.COURSES_VIEW_SCHEDULE_AND_DETAILS, +] + +COURSE_AUDITOR = RoleData(external_key="course_auditor", permissions=COURSE_AUDITOR_PERMISSIONS) + +COURSE_EDITOR_PERMISSIONS = [ + permissions.COURSES_VIEW_COURSE, + permissions.COURSES_VIEW_COURSE_UPDATES, + permissions.COURSES_VIEW_PAGES_AND_RESOURCES, + permissions.COURSES_VIEW_FILES, + permissions.COURSES_VIEW_GRADING_SETTINGS, + permissions.COURSES_VIEW_CHECKLISTS, + permissions.COURSES_VIEW_COURSE_TEAM, + permissions.COURSES_VIEW_SCHEDULE_AND_DETAILS, + permissions.COURSES_EDIT_COURSE_CONTENT, + permissions.COURSES_MANAGE_LIBRARY_UPDATES, + permissions.COURSES_MANAGE_COURSE_UPDATES, + permissions.COURSES_MANAGE_PAGES_AND_RESOURCES, + permissions.COURSES_CREATE_FILES, + permissions.COURSES_EDIT_FILES, + permissions.COURSES_EDIT_GRADING_SETTINGS, + permissions.COURSES_MANAGE_GROUP_CONFIGURATIONS, + permissions.COURSES_EDIT_DETAILS, + permissions.COURSES_MANAGE_TAGS, +] + +COURSE_EDITOR = RoleData(external_key="course_editor", permissions=COURSE_EDITOR_PERMISSIONS) + +COURSE_ADMIN_PERMISSIONS = [ + permissions.COURSES_LEGACY_INSTRUCTOR_ROLE_PERMISSIONS, + permissions.COURSES_VIEW_COURSE, + permissions.COURSES_VIEW_COURSE_UPDATES, + permissions.COURSES_VIEW_PAGES_AND_RESOURCES, + permissions.COURSES_VIEW_FILES, + permissions.COURSES_VIEW_GRADING_SETTINGS, + permissions.COURSES_VIEW_CHECKLISTS, + permissions.COURSES_VIEW_COURSE_TEAM, + permissions.COURSES_VIEW_SCHEDULE_AND_DETAILS, + permissions.COURSES_EDIT_COURSE_CONTENT, + permissions.COURSES_MANAGE_LIBRARY_UPDATES, + permissions.COURSES_MANAGE_COURSE_UPDATES, + permissions.COURSES_MANAGE_PAGES_AND_RESOURCES, + permissions.COURSES_CREATE_FILES, + permissions.COURSES_EDIT_FILES, + permissions.COURSES_EDIT_GRADING_SETTINGS, + permissions.COURSES_MANAGE_GROUP_CONFIGURATIONS, + permissions.COURSES_EDIT_DETAILS, + permissions.COURSES_MANAGE_TAGS, + permissions.COURSES_PUBLISH_COURSE_CONTENT, + permissions.COURSES_DELETE_FILES, + permissions.COURSES_EDIT_SCHEDULE, + permissions.COURSES_MANAGE_ADVANCED_SETTINGS, + permissions.COURSES_MANAGE_CERTIFICATES, + permissions.COURSES_IMPORT_COURSE, + permissions.COURSES_EXPORT_COURSE, + permissions.COURSES_EXPORT_TAGS, + permissions.COURSES_MANAGE_COURSE_TEAM, + permissions.COURSES_MANAGE_TAXONOMIES, +] + +COURSE_ADMIN = RoleData(external_key="course_admin", permissions=COURSE_ADMIN_PERMISSIONS) COURSE_STAFF_PERMISSIONS = [ - permissions.MANAGE_ADVANCED_SETTINGS, + permissions.COURSES_LEGACY_STAFF_ROLE_PERMISSIONS, + permissions.COURSES_VIEW_COURSE, + permissions.COURSES_VIEW_COURSE_UPDATES, + permissions.COURSES_VIEW_PAGES_AND_RESOURCES, + permissions.COURSES_VIEW_FILES, + permissions.COURSES_VIEW_GRADING_SETTINGS, + permissions.COURSES_VIEW_CHECKLISTS, + permissions.COURSES_VIEW_COURSE_TEAM, + permissions.COURSES_VIEW_SCHEDULE_AND_DETAILS, + permissions.COURSES_EDIT_COURSE_CONTENT, + permissions.COURSES_MANAGE_LIBRARY_UPDATES, + permissions.COURSES_MANAGE_COURSE_UPDATES, + permissions.COURSES_MANAGE_PAGES_AND_RESOURCES, + permissions.COURSES_CREATE_FILES, + permissions.COURSES_EDIT_FILES, + permissions.COURSES_EDIT_GRADING_SETTINGS, + permissions.COURSES_MANAGE_GROUP_CONFIGURATIONS, + permissions.COURSES_EDIT_DETAILS, + permissions.COURSES_MANAGE_TAGS, + permissions.COURSES_PUBLISH_COURSE_CONTENT, + permissions.COURSES_DELETE_FILES, + permissions.COURSES_EDIT_SCHEDULE, + permissions.COURSES_MANAGE_ADVANCED_SETTINGS, + permissions.COURSES_MANAGE_CERTIFICATES, + permissions.COURSES_IMPORT_COURSE, + permissions.COURSES_EXPORT_COURSE, + permissions.COURSES_EXPORT_TAGS, ] COURSE_STAFF = RoleData(external_key="course_staff", permissions=COURSE_STAFF_PERMISSIONS) + +COURSE_LIMITED_STAFF_PERMISSIONS = [ + permissions.COURSES_LEGACY_LIMITED_STAFF_ROLE_PERMISSIONS, +] + +COURSE_LIMITED_STAFF = RoleData(external_key="course_limited_staff", permissions=COURSE_LIMITED_STAFF_PERMISSIONS) + +COURSE_DATA_RESEARCHER_PERMISSIONS = [ + permissions.COURSES_LEGACY_DATA_RESEARCHER_PERMISSIONS, +] + +COURSE_DATA_RESEARCHER = RoleData(external_key="course_data_researcher", permissions=COURSE_DATA_RESEARCHER_PERMISSIONS) + +COURSE_BETA_TESTER_PERMISSIONS = [ + permissions.COURSES_LEGACY_BETA_TESTER_PERMISSIONS, +] + +COURSE_BETA_TESTER = RoleData(external_key="course_beta_tester", permissions=COURSE_BETA_TESTER_PERMISSIONS) + +# Map of legacy course role names to their equivalent new roles +# This mapping must be unique in both directions, since it may be used as a reverse lookup (value → key). +# If multiple keys share the same value, it will lead to collisions. +LEGACY_COURSE_ROLE_EQUIVALENCES = { + "instructor": COURSE_ADMIN.external_key, + "staff": COURSE_STAFF.external_key, + "limited_staff": COURSE_LIMITED_STAFF.external_key, + "data_researcher": COURSE_DATA_RESEARCHER.external_key, + "beta_testers": COURSE_BETA_TESTER.external_key, +} diff --git a/openedx_authz/engine/config/authz.policy b/openedx_authz/engine/config/authz.policy index d1405bbd..4dfb3d1b 100644 --- a/openedx_authz/engine/config/authz.policy +++ b/openedx_authz/engine/config/authz.policy @@ -70,7 +70,105 @@ g2, act^content_libraries.create_library_collection, act^content_libraries.edit_ g2, act^content_libraries.edit_library_collection, act^content_libraries.view_library -# Course Policies +## Course Policies -# Course Staff Permissions +# Course Auditor Role Policies +p, role^course_auditor, act^courses.view_course, course-v1^*, allow +p, role^course_auditor, act^courses.view_course_updates, course-v1^*, allow +p, role^course_auditor, act^courses.view_pages_and_resources, course-v1^*, allow +p, role^course_auditor, act^courses.view_files, course-v1^*, allow +p, role^course_auditor, act^courses.view_grading_settings, course-v1^*, allow +p, role^course_auditor, act^courses.view_checklists, course-v1^*, allow +p, role^course_auditor, act^courses.view_course_team, course-v1^*, allow +p, role^course_auditor, act^courses.view_schedule_and_details, course-v1^*, allow + +# Course Editor Role Policies +p, role^course_editor, act^courses.view_course, course-v1^*, allow +p, role^course_editor, act^courses.view_course_updates, course-v1^*, allow +p, role^course_editor, act^courses.view_pages_and_resources, course-v1^*, allow +p, role^course_editor, act^courses.view_files, course-v1^*, allow +p, role^course_editor, act^courses.view_grading_settings, course-v1^*, allow +p, role^course_editor, act^courses.view_checklists, course-v1^*, allow +p, role^course_editor, act^courses.view_course_team, course-v1^*, allow +p, role^course_editor, act^courses.view_schedule_and_details, course-v1^*, allow +p, role^course_editor, act^courses.edit_course_content, course-v1^*, allow +p, role^course_editor, act^courses.manage_library_updates, course-v1^*, allow +p, role^course_editor, act^courses.manage_course_updates, course-v1^*, allow +p, role^course_editor, act^courses.manage_pages_and_resources, course-v1^*, allow +p, role^course_editor, act^courses.create_files, course-v1^*, allow +p, role^course_editor, act^courses.edit_files, course-v1^*, allow +p, role^course_editor, act^courses.edit_grading_settings, course-v1^*, allow +p, role^course_editor, act^courses.manage_group_configurations, course-v1^*, allow +p, role^course_editor, act^courses.edit_details, course-v1^*, allow +p, role^course_editor, act^courses.manage_tags, course-v1^*, allow + +# Course Staff Role Policies +p, role^course_staff, act^courses.legacy_staff_role_permissions, course-v1^*, allow +p, role^course_staff, act^courses.view_course, course-v1^*, allow +p, role^course_staff, act^courses.view_course_updates, course-v1^*, allow +p, role^course_staff, act^courses.view_pages_and_resources, course-v1^*, allow +p, role^course_staff, act^courses.view_files, course-v1^*, allow +p, role^course_staff, act^courses.view_grading_settings, course-v1^*, allow +p, role^course_staff, act^courses.view_checklists, course-v1^*, allow +p, role^course_staff, act^courses.view_course_team, course-v1^*, allow +p, role^course_staff, act^courses.view_schedule_and_details, course-v1^*, allow +p, role^course_staff, act^courses.edit_course_content, course-v1^*, allow +p, role^course_staff, act^courses.manage_library_updates, course-v1^*, allow +p, role^course_staff, act^courses.manage_course_updates, course-v1^*, allow +p, role^course_staff, act^courses.manage_pages_and_resources, course-v1^*, allow +p, role^course_staff, act^courses.create_files, course-v1^*, allow +p, role^course_staff, act^courses.edit_files, course-v1^*, allow +p, role^course_staff, act^courses.edit_grading_settings, course-v1^*, allow +p, role^course_staff, act^courses.manage_group_configurations, course-v1^*, allow +p, role^course_staff, act^courses.edit_details, course-v1^*, allow +p, role^course_staff, act^courses.manage_tags, course-v1^*, allow +p, role^course_staff, act^courses.publish_course_content, course-v1^*, allow +p, role^course_staff, act^courses.delete_files, course-v1^*, allow +p, role^course_staff, act^courses.edit_schedule, course-v1^*, allow p, role^course_staff, act^courses.manage_advanced_settings, course-v1^*, allow +p, role^course_staff, act^courses.manage_certificates, course-v1^*, allow +p, role^course_staff, act^courses.import_course, course-v1^*, allow +p, role^course_staff, act^courses.export_course, course-v1^*, allow +p, role^course_staff, act^courses.export_tags, course-v1^*, allow + + +# Course Admin Role Policies +p, role^course_admin, act^courses.legacy_instructor_role_permissions, course-v1^*, allow +p, role^course_admin, act^courses.view_course, course-v1^*, allow +p, role^course_admin, act^courses.view_course_updates, course-v1^*, allow +p, role^course_admin, act^courses.view_pages_and_resources, course-v1^*, allow +p, role^course_admin, act^courses.view_files, course-v1^*, allow +p, role^course_admin, act^courses.view_grading_settings, course-v1^*, allow +p, role^course_admin, act^courses.view_checklists, course-v1^*, allow +p, role^course_admin, act^courses.view_course_team, course-v1^*, allow +p, role^course_admin, act^courses.view_schedule_and_details, course-v1^*, allow +p, role^course_admin, act^courses.edit_course_content, course-v1^*, allow +p, role^course_admin, act^courses.manage_library_updates, course-v1^*, allow +p, role^course_admin, act^courses.manage_course_updates, course-v1^*, allow +p, role^course_admin, act^courses.manage_pages_and_resources, course-v1^*, allow +p, role^course_admin, act^courses.create_files, course-v1^*, allow +p, role^course_admin, act^courses.edit_files, course-v1^*, allow +p, role^course_admin, act^courses.edit_grading_settings, course-v1^*, allow +p, role^course_admin, act^courses.manage_group_configurations, course-v1^*, allow +p, role^course_admin, act^courses.edit_details, course-v1^*, allow +p, role^course_admin, act^courses.manage_tags, course-v1^*, allow +p, role^course_admin, act^courses.publish_course_content, course-v1^*, allow +p, role^course_admin, act^courses.delete_files, course-v1^*, allow +p, role^course_admin, act^courses.edit_schedule, course-v1^*, allow +p, role^course_admin, act^courses.manage_advanced_settings, course-v1^*, allow +p, role^course_admin, act^courses.manage_certificates, course-v1^*, allow +p, role^course_admin, act^courses.import_course, course-v1^*, allow +p, role^course_admin, act^courses.export_course, course-v1^*, allow +p, role^course_admin, act^courses.export_tags, course-v1^*, allow +p, role^course_admin, act^courses.manage_course_team, course-v1^*, allow +p, role^course_admin, act^courses.manage_taxonomies, course-v1^*, allow + + +# Course Limited Staff Role Policies (legacy role) +p, role^course_limited_staff, act^courses.legacy_limited_staff_role_permissions, course-v1^*, allow + +# Course Data Researcher Role Policies (legacy role) +p, role^course_data_researcher, act^courses.legacy_data_researcher_permissions, course-v1^*, allow + +# Course Beta Tester Role Policies (legacy role) +p, role^course_beta_tester, act^courses.legacy_beta_tester_permissions, course-v1^*, allow diff --git a/openedx_authz/tests/api/test_users.py b/openedx_authz/tests/api/test_users.py index 26008f23..2da05e21 100644 --- a/openedx_authz/tests/api/test_users.py +++ b/openedx_authz/tests/api/test_users.py @@ -431,19 +431,64 @@ class TestUserPermissions(UserAssignmentsSetupMixin): @data( # Course permissions - ("daniel", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T1", True), - ("daniel", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T2", False), - ("judy", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T1", False), - ("judy", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T2", True), + ( + "daniel", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T1", + True, + ), + ( + "daniel", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T2", + False, + ), + ( + "judy", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T1", + False, + ), + ("judy", permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T2", True), # Multiple subjects with same role in same scope - ("maria", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T3", True), - ("aida", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T3", True), - ("maria", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T1", False), - ("aida", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T1", False), + ( + "maria", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T3", + True, + ), + ("aida", permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T3", True), + ( + "maria", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T1", + False, + ), + ( + "aida", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T1", + False, + ), # Same user, same role, different scopes - ("carlos", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T1", True), - ("carlos", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T2", True), - ("carlos", permissions.MANAGE_ADVANCED_SETTINGS.identifier, "course-v1:TestOrg+TestCourse+2024_T3", True), + ( + "carlos", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T1", + True, + ), + ( + "carlos", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T2", + True, + ), + ( + "carlos", + permissions.COURSES_MANAGE_ADVANCED_SETTINGS.identifier, + "course-v1:TestOrg+TestCourse+2024_T3", + True, + ), # Library permissions ("alice", permissions.DELETE_LIBRARY.identifier, "lib:Org1:math_101", True), ("bob", permissions.PUBLISH_LIBRARY_CONTENT.identifier, "lib:Org1:history_201", True), diff --git a/openedx_authz/tests/test_enforcer.py b/openedx_authz/tests/test_enforcer.py index e213a3bb..e35f9283 100644 --- a/openedx_authz/tests/test_enforcer.py +++ b/openedx_authz/tests/test_enforcer.py @@ -401,7 +401,10 @@ def test_multi_scope_filtering(self): org_scope = "org^*" expected_lib_count = self._count_policies_in_file(scope_pattern=lib_scope) + expected_course_count = self._count_policies_in_file(scope_pattern=course_scope) self._add_test_policies_for_multiple_scopes() + # The previous function added 6 custom policies for courses, add to expected + expected_course_count += 6 self._load_policies_for_scope(lib_scope) lib_count = len(global_enforcer.get_policy()) @@ -413,7 +416,7 @@ def test_multi_scope_filtering(self): org_count = len(global_enforcer.get_policy()) self.assertEqual(lib_count, expected_lib_count) - self.assertEqual(course_count, 7) + self.assertEqual(course_count, expected_course_count) self.assertEqual(org_count, 3) global_enforcer.clear_policy() diff --git a/openedx_authz/tests/test_engine_utils.py b/openedx_authz/tests/test_engine_utils.py index 33dc8708..5315c97f 100644 --- a/openedx_authz/tests/test_engine_utils.py +++ b/openedx_authz/tests/test_engine_utils.py @@ -76,10 +76,10 @@ def test_migrate_all_file_policies_to_database(self): Expected Result: - All policies from the file are loaded into the database - - The file contains 32 regular policies (p rules) + - The file contains 116 regular policies (p rules) - Policy content matches expected file content """ - expected_policy_count = 32 + expected_policy_count = 116 migrate_policy_between_enforcers(self.source_enforcer, self.target_enforcer) self.target_enforcer.load_policy() @@ -208,7 +208,7 @@ def test_migrate_complete_file_contents(self): """Test that all policy types from the file are migrated correctly. Expected Result: - - All regular policies (p) are migrated (31 rules) + - All regular policies (p) are migrated (116 rules) - No role assignments (g) - these come from database - All action inheritance rules (g2) are migrated (10 rules) """ @@ -216,8 +216,8 @@ def test_migrate_complete_file_contents(self): self.assertEqual( len(self.target_enforcer.get_policy()), - 32, - "Should have 31 regular policies from file", + 116, + "Should have 116 regular policies from file", ) self.assertEqual( len(self.target_enforcer.get_grouping_policy()), @@ -250,8 +250,8 @@ def test_migrate_partial_duplicates(self): target_policies = self.target_enforcer.get_policy() self.assertEqual( len(target_policies), - 32, - "Should have 32 policies total, with no duplicates", + 116, + "Should have 116 policies total, with no duplicates", ) duplicates = CasbinRule.objects.values("v0", "v1", "v2").annotate(total=Count("*")).filter(total__gt=1) @@ -346,7 +346,7 @@ def test_migrate_preserves_existing_db_policies(self): migrate_policy_between_enforcers(self.source_enforcer, self.target_enforcer) target_policies = self.target_enforcer.get_policy() - self.assertEqual(len(target_policies), 33, "Should have 32 file policies + 1 custom policy") + self.assertEqual(len(target_policies), 117, "Should have 116 file policies + 1 custom policy") self.assertIn(custom_policy, target_policies, "Custom database policy should be preserved") def test_migrate_preserves_user_role_assignments_in_db(self): @@ -382,4 +382,4 @@ def test_migrate_preserves_user_role_assignments_in_db(self): ) target_policies = self.target_enforcer.get_policy() - self.assertEqual(len(target_policies), 32, "All 32 policies from file should be loaded") + self.assertEqual(len(target_policies), 116, "All 116 policies from file should be loaded")