openedx-authz 2.3.0__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/CHANGELOG.rst +5 -0
  2. {openedx_authz-2.3.0/src/openedx_authz.egg-info → openedx_authz-2.5.0}/PKG-INFO +1 -1
  3. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0026-rest-api-domain-ownership-boundary.rst +7 -0
  4. openedx_authz-2.5.0/docs/decisions/0032-applets-for-built-in-authorization-capabilities.rst +190 -0
  5. openedx_authz-2.5.0/docs/decisions/0033-shared-applet-for-course-team-roles.rst +43 -0
  6. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/pyproject.toml +1 -0
  7. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/authz/schema/course_roles.yaml +0 -2
  8. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/authz-schema-v1.json +11 -3
  9. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/validation.py +16 -0
  10. openedx_authz-2.5.0/src/openedx_authz/tests/schema/test_json_schema.py +48 -0
  11. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_renderer.py +25 -1
  12. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_validation.py +10 -0
  13. {openedx_authz-2.3.0 → openedx_authz-2.5.0/src/openedx_authz.egg-info}/PKG-INFO +1 -1
  14. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/SOURCES.txt +3 -0
  15. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/scm_file_list.json +3 -0
  16. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/uv.lock +10 -0
  17. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.annotation_safe_list.yml +0 -0
  18. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.editorconfig +0 -0
  19. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  20. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/dependabot.yml +0 -0
  21. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/add-depr-ticket-to-depr-board.yml +0 -0
  22. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/add-remove-label-on-comment.yml +0 -0
  23. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/ci.yml +0 -0
  24. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/commitlint.yml +0 -0
  25. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/release.yml +0 -0
  26. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/self-assign-issue.yml +0 -0
  27. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.github/workflows/upgrade-python-requirements.yml +0 -0
  28. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.gitignore +0 -0
  29. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.pii_annotations.yml +0 -0
  30. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/.readthedocs.yaml +0 -0
  31. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/LICENSE +0 -0
  32. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/MANIFEST.in +0 -0
  33. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/Makefile +0 -0
  34. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/README.rst +0 -0
  35. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/catalog-info.yaml +0 -0
  36. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/codecov.yml +0 -0
  37. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/Makefile +0 -0
  38. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/_images/architecture-overview.png +0 -0
  39. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/_static/theme_overrides.css +0 -0
  40. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/changelog.rst +0 -0
  41. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/concepts/core_roles_and_permissions/content_library_roles.rst +0 -0
  42. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/concepts/core_roles_and_permissions/course_roles.rst +0 -0
  43. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/concepts/core_roles_and_permissions/index.rst +0 -0
  44. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/concepts/index.rst +0 -0
  45. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/conf.py +0 -0
  46. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0001-purpose-of-this-repo.rst +0 -0
  47. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0002-authorization-model-foundation.rst +0 -0
  48. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0003-jwt-usage.rst +0 -0
  49. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0004-technology-selection.rst +0 -0
  50. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0005-architecture-and-data-modeling.rst +0 -0
  51. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0007-enforcement-mechanisms-mfe.rst +0 -0
  52. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0008-compatibility-scheme.rst +0 -0
  53. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0009-authz-for-course-authoring.rst +0 -0
  54. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0010-course-authoring-flag.rst +0 -0
  55. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0011-course-authoring-migration-process.rst +0 -0
  56. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0012-auditability.rst +0 -0
  57. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0012-glob-support-for-role-assignments.rst +0 -0
  58. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0013-course-authoring-automatic-migration.rst +0 -0
  59. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0014-bulk-assignment-queries-without-casbin-enforce.rst +0 -0
  60. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0015-expose-course-authoring-waffle-flag-state-via-rest-api.rst +0 -0
  61. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0016-static-and-dynamic-roles.rst +0 -0
  62. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0017-static-authorization-schema.rst +0 -0
  63. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0018-authorization-schema-lifecycle.rst +0 -0
  64. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0019-authorization-schema-discovery.rst +0 -0
  65. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0020-authorization-schema-internationalization.rst +0 -0
  66. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0021-authorization-definition-api.rst +0 -0
  67. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0022-defer-authorization-implications.rst +0 -0
  68. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0023-extend-static-roles.rst +0 -0
  69. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0024-api-contract-for-user-grouped-role-assignments.rst +0 -0
  70. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0025-authorization-schema-source-tracking.rst +0 -0
  71. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0027-cross-domain-filtering-via-openedx-filters.rst +0 -0
  72. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0028-api-contract-for-role-catalog.rst +0 -0
  73. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0029-paragon-icon-list-maintenance.rst +0 -0
  74. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0030-tutor-implementation-authz-schema-discovery.rst +0 -0
  75. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/0031-priority-applies-to-role-extensions-only.rst +0 -0
  76. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions/README.rst +0 -0
  77. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/decisions.rst +0 -0
  78. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/getting_started.rst +0 -0
  79. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/how-tos/index.rst +0 -0
  80. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/index.rst +0 -0
  81. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/internationalization.rst +0 -0
  82. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/make.bat +0 -0
  83. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/quickstarts/index.rst +0 -0
  84. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/readme.rst +0 -0
  85. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/references/authorization-schema-json.rst +0 -0
  86. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/references/authorization-schema.rst +0 -0
  87. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/references/index.rst +0 -0
  88. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/docs/testing.rst +0 -0
  89. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/manage.py +0 -0
  90. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/pylintrc +0 -0
  91. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/pylintrc_tweaks +0 -0
  92. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/ruff.toml +0 -0
  93. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/scripts/generate_paragon_icons.py +0 -0
  94. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/setup.cfg +0 -0
  95. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/__init__.py +0 -0
  96. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/admin.py +0 -0
  97. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/__init__.py +0 -0
  98. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/catalog.py +0 -0
  99. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/data.py +0 -0
  100. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/permissions.py +0 -0
  101. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/roles.py +0 -0
  102. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/users.py +0 -0
  103. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/api/utils.py +0 -0
  104. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/apps.py +0 -0
  105. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/authz/__init__.py +0 -0
  106. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/authz/schema/course_permissions.yaml +0 -0
  107. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/authz/schema/library_permissions.yaml +0 -0
  108. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/authz/schema/library_roles.yaml +0 -0
  109. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/conf/locale/config.yaml +0 -0
  110. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/constants/__init__.py +0 -0
  111. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/constants/permissions.py +0 -0
  112. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/constants/roles.py +0 -0
  113. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/constants/schema.py +0 -0
  114. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/data.py +0 -0
  115. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/__init__.py +0 -0
  116. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/adapter.py +0 -0
  117. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/apps.py +0 -0
  118. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/config/model.conf +0 -0
  119. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/enforcer.py +0 -0
  120. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/filter.py +0 -0
  121. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/matcher.py +0 -0
  122. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/policy.py +0 -0
  123. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/renderer.py +0 -0
  124. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/__init__.py +0 -0
  125. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/compilation.py +0 -0
  126. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/discovery.py +0 -0
  127. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/exceptions.py +0 -0
  128. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/issues.py +0 -0
  129. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/loading.py +0 -0
  130. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/paragon_icons.py +0 -0
  131. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/parsing.py +0 -0
  132. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/pipeline.py +0 -0
  133. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/syntax.py +0 -0
  134. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/translation.py +0 -0
  135. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/types/__init__.py +0 -0
  136. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/types/compilation.py +0 -0
  137. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/types/definitions.py +0 -0
  138. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/schema/types/loading.py +0 -0
  139. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/engine/utils.py +0 -0
  140. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/filters.py +0 -0
  141. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/handlers.py +0 -0
  142. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/__init__.py +0 -0
  143. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/__init__.py +0 -0
  144. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/authz_migrate_course_authoring.py +0 -0
  145. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/authz_rollback_course_authoring.py +0 -0
  146. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/enforcement.py +0 -0
  147. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/extract_schema_translations.py +0 -0
  148. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/load_authz_schema.py +0 -0
  149. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/management/commands/load_policies.py +0 -0
  150. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0001_add_casbin_dependency.py +0 -0
  151. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0002_initial.py +0 -0
  152. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0003_usersubject.py +0 -0
  153. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0004_contentlibraryscope.py +0 -0
  154. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0005_policycachecontrol.py +0 -0
  155. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0006_migrate_legacy_permissions.py +0 -0
  156. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0007_coursescope.py +0 -0
  157. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0008_authzcourseauthoringmigrationrun.py +0 -0
  158. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0009_roleassignmentaudit.py +0 -0
  159. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0010_scope_external_key.py +0 -0
  160. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/0011_authz_schema_definitions.py +0 -0
  161. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/migrations/__init__.py +0 -0
  162. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/__init__.py +0 -0
  163. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/authz_migration.py +0 -0
  164. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/base.py +0 -0
  165. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/core.py +0 -0
  166. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/engine.py +0 -0
  167. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/schema.py +0 -0
  168. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/scopes.py +0 -0
  169. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/models/subjects.py +0 -0
  170. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/__init__.py +0 -0
  171. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/data.py +0 -0
  172. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/decorators.py +0 -0
  173. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/urls.py +0 -0
  174. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/utils.py +0 -0
  175. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/__init__.py +0 -0
  176. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/admin_console/__init__.py +0 -0
  177. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/admin_console/filters.py +0 -0
  178. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/admin_console/serializers.py +0 -0
  179. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/admin_console/views.py +0 -0
  180. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/course_authoring/__init__.py +0 -0
  181. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/course_authoring/pipeline.py +0 -0
  182. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/course_authoring/views.py +0 -0
  183. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/fields.py +0 -0
  184. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/paginators.py +0 -0
  185. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/permissions.py +0 -0
  186. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/serializers.py +0 -0
  187. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/urls.py +0 -0
  188. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/rest_api/v1/views.py +0 -0
  189. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/settings/__init__.py +0 -0
  190. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/settings/common.py +0 -0
  191. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/settings/production.py +0 -0
  192. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/settings/test.py +0 -0
  193. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/templates/openedx_authz/base.html +0 -0
  194. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/__init__.py +0 -0
  195. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/api/__init__.py +0 -0
  196. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/api/test_catalog.py +0 -0
  197. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/api/test_data.py +0 -0
  198. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/api/test_roles.py +0 -0
  199. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/api/test_users.py +0 -0
  200. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/authz/__init__.py +0 -0
  201. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/authz/test_resources.py +0 -0
  202. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/__init__.py +0 -0
  203. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/conftest.py +0 -0
  204. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/categories.yaml +0 -0
  205. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/extensions.yaml +0 -0
  206. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/permissions.yaml +0 -0
  207. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/roles.yaml +0 -0
  208. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_parse/malformed.yaml +0 -0
  209. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_semantics/unknown_category.yaml +0 -0
  210. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_structure/items.yml +0 -0
  211. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_structure/priority.yaml +0 -0
  212. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/categories.yaml +0 -0
  213. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/extensions.yaml +0 -0
  214. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/permissions.yml +0 -0
  215. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/roles.yaml +0 -0
  216. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/test_handlers.py +0 -0
  217. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/test_models.py +0 -0
  218. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/test_schema_apply.py +0 -0
  219. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/test_schema_pipeline.py +0 -0
  220. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/integration/test_views.py +0 -0
  221. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/__init__.py +0 -0
  222. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/admin_console/__init__.py +0 -0
  223. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/admin_console/test_views.py +0 -0
  224. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/course_authoring/__init__.py +0 -0
  225. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/course_authoring/test_pipeline.py +0 -0
  226. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/course_authoring/test_views.py +0 -0
  227. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/mixins.py +0 -0
  228. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/test_permissions.py +0 -0
  229. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/test_utils.py +0 -0
  230. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/rest_api/test_views.py +0 -0
  231. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/__init__.py +0 -0
  232. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/factories.py +0 -0
  233. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_apply.py +0 -0
  234. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_compilation.py +0 -0
  235. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_discovery.py +0 -0
  236. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_load_authz_schema_command.py +0 -0
  237. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_loading.py +0 -0
  238. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_parsing.py +0 -0
  239. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_pipeline.py +0 -0
  240. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_schema_models.py +0 -0
  241. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_source_storage.py +0 -0
  242. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_syntax.py +0 -0
  243. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_translation.py +0 -0
  244. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/schema/test_types.py +0 -0
  245. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/__init__.py +0 -0
  246. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/apps.py +0 -0
  247. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/migrations/0001_initial.py +0 -0
  248. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/migrations/0002_learningpackage_contentlibrary_learning_package.py +0 -0
  249. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/migrations/0002_userprofile.py +0 -0
  250. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/stubs/models.py +0 -0
  251. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_commands.py +0 -0
  252. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_enforcement.py +0 -0
  253. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_enforcer.py +0 -0
  254. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_engine_utils.py +0 -0
  255. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_filter.py +0 -0
  256. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_handlers.py +0 -0
  257. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_imports.py +0 -0
  258. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_migrations.py +0 -0
  259. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_models.py +0 -0
  260. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_policy.py +0 -0
  261. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/tests/test_utils.py +0 -0
  262. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/urls.py +0 -0
  263. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz/utils.py +0 -0
  264. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/dependency_links.txt +0 -0
  265. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/entry_points.txt +0 -0
  266. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/requires.txt +0 -0
  267. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/src/openedx_authz.egg-info/top_level.txt +0 -0
  268. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tox.ini +0 -0
  269. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/.gitignore +0 -0
  270. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/LICENSE.txt +0 -0
  271. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/MANIFEST.in +0 -0
  272. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/Makefile +0 -0
  273. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/README.rst +0 -0
  274. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/pyproject.toml +0 -0
  275. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/tutor_authz/__about__.py +0 -0
  276. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/tutor_authz/__init__.py +0 -0
  277. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/tutor_authz/patches/openedx-common-settings +0 -0
  278. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/tutor_authz/plugin.py +0 -0
  279. {openedx_authz-2.3.0 → openedx_authz-2.5.0}/tutor/tutor-contrib-authz/tutor_authz/templates/apps/openedx/config/authz-schema.yml +0 -0
@@ -52,6 +52,11 @@ Added
52
52
  ``openedx_authz/engine/schema/paragon_icons.py`` and regenerated with
53
53
  ``make paragon_icons`` (ADR 0029).
54
54
 
55
+ Changed
56
+ ========
57
+
58
+ * Static roles may declare an empty permission list when ``role_extensions`` contribute their permissions. Permission lists used by extension edits remain non-empty.
59
+
55
60
  Fixed
56
61
  =====
57
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-authz
3
- Version: 2.3.0
3
+ Version: 2.5.0
4
4
  Summary: Open edX AuthZ provides the architecture and foundations of the authorization framework.
5
5
  Author-email: Open edX Project <oscm@openedx.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -105,6 +105,11 @@ Rejected Alternatives
105
105
  **Adding application-specific visibility rules to reusable endpoint code**
106
106
  This would make reusable authorization code interpret data that authorization does not own. A general hook keeps the rule in a separate implementation, and `openedx_catalog`_ follows a related approach for installation-specific visibility rules.
107
107
 
108
+ Addendum 2026-10-20
109
+ *******************
110
+
111
+ `ADR 0032`_ replaces the package layout in decisions 4, 6, and 8, so client-specific and cross-domain endpoints now belong in the applet for the capability they serve, while the ownership rules in this ADR still apply.
112
+
108
113
  References
109
114
  **********
110
115
 
@@ -115,6 +120,7 @@ References
115
120
  * `Issue #377`_
116
121
  * `PR #361`_
117
122
  * `openedx_catalog`_
123
+ * `ADR 0032`_
118
124
 
119
125
  .. _edX DDD Bounded Contexts: https://openedx.atlassian.net/wiki/spaces/AC/pages/663224968/edX+DDD+Bounded+Contexts
120
126
  .. _ADR 0018 in openedx-events: https://github.com/openedx/openedx-events/blob/main/docs/decisions/0018-supporting-subdomain-modules.rst
@@ -123,3 +129,4 @@ References
123
129
  .. _Issue #377: https://github.com/openedx/openedx-authz/issues/377
124
130
  .. _PR #361: https://github.com/openedx/openedx-authz/pull/361
125
131
  .. _openedx_catalog: https://github.com/openedx/openedx-core/blob/main/src/openedx_catalog/api.py
132
+ .. _ADR 0032: 0032-applets-for-built-in-authorization-capabilities.rst
@@ -0,0 +1,190 @@
1
+ 0032: Applets for Built-In Authorization Capabilities
2
+ #####################################################
3
+
4
+ Status
5
+ ******
6
+
7
+ **Draft**
8
+
9
+ Context
10
+ *******
11
+
12
+ `ADR 0005`_ defines ``openedx_authz.api`` as the public API for the reusable authorization framework, although the package also ships the Open edX-specific roles and permissions that the platform uses, as YAML schema files and Python constants. These definitions are grouped by type, so ``authz/schema/course_permissions.yaml`` defines every course permission and ``authz/schema/course_roles.yaml`` lists the complete permissions for each course role.
13
+
14
+ Grouping by type keeps roles apart from permissions, but it also puts definitions from unrelated responsibilities into the same course-level files, so a reader can see that a permission applies to a course without being able to tell whether enrollments, grading, or another area owns it.
15
+
16
+ `Issue #484`_ defines permissions for the Instructor Dashboard, which brings together several areas of course administration, including enrollments, the gradebook, learner extensions, and cohorts. The `edX DDD Bounded Contexts`_ map places those areas in different parts of the platform, so adding their permissions to the shared files would mix definitions from several bounded contexts and make those files the place where every new contribution has to be added.
17
+
18
+ The existing decisions do not say where these definitions belong, because `ADR 0026`_ defines ownership and placement rules only for REST endpoints, while `PR #421`_ raised the same ownership question for constants and schema definitions.
19
+
20
+ `ADR 0010 in openedx-core`_ answers a similar question for a single Django app by giving each bounded responsibility its own internal package, which it calls an applet. The authorization schema can already support that structure, since `ADR 0019`_ allows a package to load several schema directories and `ADR 0023`_ allows one schema contribution to extend a role defined by another.
21
+
22
+ Decision
23
+ ********
24
+
25
+ We will organize built-in Open edX authorization capabilities as applets under ``openedx_authz.applets``, while the existing public API and import paths remain stable, since ``openedx_authz.constants`` re-exports each applet's constants and the ``authz.schema`` provider lists each applet's schema directory.
26
+
27
+ 1. Applet boundaries
28
+ ====================
29
+
30
+ An applet is an internal Python package that gives one bounded responsibility a concrete place inside the existing ``openedx_authz`` Django app and keeps the related YAML definitions, Python constants, REST endpoints, filters, and tests together when those elements exist:
31
+
32
+ .. code-block:: text
33
+
34
+ openedx_authz/applets/
35
+ course_team_roles/
36
+ constants.py
37
+ schema/
38
+ enrollments/
39
+ constants.py
40
+ schema/
41
+ course_authoring/
42
+ constants.py
43
+ schema/
44
+ rest_api/
45
+ ...
46
+
47
+ The `edX DDD Bounded Contexts`_ map says where each responsibility belongs, but it does not prescribe a package layout inside one Django app, so applets turn those boundaries into Python packages that keep the code and definitions for each responsibility together. Reusable schema handling, enforcement, persistence, and public APIs stay in their existing top-level packages, since every applet depends on them.
48
+
49
+ Dependencies point in one direction, from clients to the public modules, from those modules to the applets, and from the applets to the shared packages:
50
+
51
+ .. code-block:: text
52
+
53
+ clients, such as openedx-platform
54
+ └── openedx_authz.api, openedx_authz.constants
55
+ └── openedx_authz.applets.<applet>
56
+ └── openedx_authz.api.data, openedx_authz.data
57
+
58
+ Applets do not import each other, since role extensions in YAML already connect their definitions. Applets are internal, so clients import constants from ``openedx_authz.constants``, and an applet can change or move without changing client code.
59
+
60
+ 2. Definition ownership
61
+ =======================
62
+
63
+ A permission belongs to the applet responsible for the action or data that the permission controls. For example, the ``enrollments`` applet defines the permissions for viewing or changing enrollments, along with their metadata, constants, and contributions to shared roles, so code elsewhere may check those permissions through the public API while their definitions remain in ``enrollments``.
64
+
65
+ A role belongs to the applet for the bounded context where it is assigned, as with the library roles, and other applets add the permissions they own through ``role_extensions``. Roles that cut across several contexts need a specific decision about where they live, which `ADR 0033`_ makes for the course-team roles.
66
+
67
+ Both rules rely on the `edX DDD Bounded Contexts`_ map, which shows the context that owns each action or data even when a user interface combines several capabilities. To place a new permission, we find the bounded context that owns its action or data in the map and add the permission to that context's applet, or create an applet when the context has none yet. Applet names describe the definitions they hold, so the following table records which bounded context each one stands for:
68
+
69
+ .. list-table::
70
+ :header-rows: 1
71
+
72
+ * - Applet
73
+ - Bounded context
74
+ - Definitions
75
+ * - ``course_authoring``
76
+ - Course Run Authoring and Learning Content Authoring
77
+ - Course authoring and course-team permissions
78
+ * - ``content_libraries``
79
+ - Learning Content Authoring and Learning Content Repository
80
+ - Library roles and permissions
81
+ * - ``enrollments``
82
+ - Content Access / Enrollments
83
+ - Viewing and changing course enrollments
84
+ * - ``learner_grading``
85
+ - Consuming Learning Content
86
+ - The gradebook and grading tools for individual learners
87
+ * - ``learner_administration``
88
+ - Support Learning
89
+ - Course information, learner extensions, and cohorts
90
+ * - ``course_team_roles``
91
+ - Shared by several bounded contexts
92
+ - Roles held by members of a course team
93
+ * - ``role_management``
94
+ - Authorization
95
+ - Admin Console role assignment workflows
96
+
97
+ 3. Course roles
98
+ ===============
99
+
100
+ Following `ADR 0033`_, ``course_team_roles`` defines the identifiers and metadata for the course-team roles, and their base definitions list only the permissions that ``course_team_roles`` owns, such as the legacy compatibility permissions, while each capability applet adds the permissions it owns through ``role_extensions``:
101
+
102
+ .. code-block:: yaml
103
+
104
+ # course_team_roles/schema/roles.yaml
105
+ roles:
106
+ - id: course_admin
107
+ display_name: Course Admin
108
+ description: Can manage the course team and all course settings.
109
+ scopes:
110
+ - course-v1
111
+ permissions:
112
+ - courses.legacy_instructor_role_permissions
113
+
114
+ # enrollments/schema/role_extensions.yaml
115
+ role_extensions:
116
+ - role_id: course_admin
117
+ add_permissions:
118
+ - courses.view_enrollments
119
+ - courses.enroll_learners
120
+
121
+ Roles such as ``course_editor`` have no permissions of their own, so schema version 1 will accept an empty permission list, while the compiled roles will still contain the complete permission lists contributed by the applets.
122
+
123
+ 4. REST endpoints
124
+ =================
125
+
126
+ REST endpoints continue to follow the ownership rules in `ADR 0026`_, and applets now give those rules a package boundary. The Admin Console endpoints therefore move to ``role_management``, since they support the same role-management workflow, although this placement does not settle the ownership of the organization data returned by ``AdminConsoleOrgsAPIView``, and ``WaffleFlagStatesAPIView`` moves to ``course_authoring``. In both cases, the existing URLs and route names remain unchanged.
127
+
128
+ 5. Imports
129
+ ==========
130
+
131
+ Although applets are internal packages, ``openedx_authz.constants.permissions`` and ``openedx_authz.constants.roles`` continue to export every existing constant, while ``COURSE_ADMIN`` and ``COURSE_STAFF`` continue to contain their complete permission lists, so existing import paths remain valid when code moves into an applet.
132
+
133
+ YAML remains the source of truth for static definitions, although each applet will maintain its Python constants manually for now, so tests compare those constants with the applet's YAML definitions, and generating them from YAML may replace this process in later work.
134
+
135
+ 6. Future extraction
136
+ ====================
137
+
138
+ The applets remain inside ``openedx-authz`` for this work, so implementing the ownership model and the permissions from `Issue #484`_ does not require coordinated changes across the services that use the library, and since their definitions describe the underlying actions and data rather than one user interface, another client can reuse the same permissions when it exposes the same responsibility.
139
+
140
+ An applet may later move to the service that owns its responsibility and contribute its definitions through the existing ``authz.schema`` entry point, although this decision does not include that extraction because the service boundary, package dependency, and compatibility path for the Python constants would need to be agreed first.
141
+
142
+ Consequences
143
+ ************
144
+
145
+ 1. Each built-in definition has an explicit owner based on the capability it protects.
146
+ 2. Adding a capability does not require adding its definitions to shared course permission and role files.
147
+ 3. The complete permission list for a course role is assembled from several schema contributions, so readers use the compiled schema or the public role constants to inspect that list.
148
+ 4. Each permission identifier appears in YAML and Python until generated constants replace the manually maintained files.
149
+ 5. The schema accepts an empty base permission list for applets that define roles whose permissions come from other applets. Validation logs a warning so contributors can confirm that the empty list is intentional and that ``role_extensions`` provide the role's permissions.
150
+ 6. The Admin Console and course-authoring endpoints move again after `ADR 0026`_, although their public URLs, route names, request and response contracts, and import paths remain stable.
151
+ 7. Reviewers must identify unwanted dependencies between applets because this decision does not add automated import-boundary checks.
152
+ 8. Applets create boundaries that can support later extraction, although moving one to another service requires a separate decision about dependencies and compatibility.
153
+
154
+ Rejected Alternatives
155
+ *********************
156
+
157
+ **Keeping all definitions in the shared files**
158
+ Definitions would remain grouped by type, leaving ownership implicit and continuing the growth of the shared course files.
159
+
160
+ **One applet for each screen, such as the Instructor Dashboard**
161
+ A permission that Studio and the dashboard both use would have two possible owners.
162
+
163
+ **A separate Django app for each feature**
164
+ Each feature would gain a separate app label and installation boundary, which the definitions and integrations covered by this decision do not require.
165
+
166
+ **Moving the definitions to their owning services now**
167
+ This would place the definitions at their eventual service boundary, but it would also require coordinating package dependencies and compatibility imports before implementing the permissions in `Issue #484`_, while the existing schema contribution mechanism leaves that move available after those boundaries are agreed.
168
+
169
+ References
170
+ **********
171
+
172
+ * `Issue #484`_
173
+ * `PR #421`_
174
+ * `ADR 0005`_
175
+ * `ADR 0010 in openedx-core`_
176
+ * `edX DDD Bounded Contexts`_
177
+ * `ADR 0019`_
178
+ * `ADR 0023`_
179
+ * `ADR 0026`_
180
+ * `ADR 0033`_
181
+
182
+ .. _Issue #484: https://github.com/openedx/openedx-authz/issues/484
183
+ .. _PR #421: https://github.com/openedx/openedx-authz/pull/421
184
+ .. _ADR 0005: 0005-architecture-and-data-modeling.rst
185
+ .. _ADR 0010 in openedx-core: https://github.com/openedx/openedx-core/blob/6a26b3a4181815a44e9d724c16857b61b484b0ea/docs/openedx_content/decisions/0010-merge-authoring-apps-into-openedx-content.rst
186
+ .. _edX DDD Bounded Contexts: https://openedx.atlassian.net/wiki/spaces/AC/pages/663224968/edX+DDD+Bounded+Contexts
187
+ .. _ADR 0019: 0019-authorization-schema-discovery.rst
188
+ .. _ADR 0023: 0023-extend-static-roles.rst
189
+ .. _ADR 0026: 0026-rest-api-domain-ownership-boundary.rst
190
+ .. _ADR 0033: 0033-shared-applet-for-course-team-roles.rst
@@ -0,0 +1,43 @@
1
+ 0033: Shared Applet for Course-Team Roles
2
+ #########################################
3
+
4
+ Status
5
+ ******
6
+
7
+ **Draft**
8
+
9
+ Context
10
+ *******
11
+
12
+ `ADR 0032`_ places a role in the applet for the bounded context where it is assigned, while roles that cut across several contexts need a specific decision about where they live. The course-team roles are the first case, since they are assigned to members of a course team and combine permissions from course authoring, enrollments, grading, and learner administration.
13
+
14
+ These roles do not all combine permissions in the same way today. ``course_admin`` and ``course_staff`` receive permissions from several applets, ``course_editor`` and ``course_auditor`` receive every permission from ``course_authoring``, and the legacy roles such as ``course_limited_staff`` only list their legacy compatibility permission. However, the `Instructor Dashboard Permissions`_ proposal allows more restricted roles to receive a subset of the dashboard permissions, so the roles that rely on one applet today may combine several later.
15
+
16
+ Decision
17
+ ********
18
+
19
+ The course-team roles are cross-cutting, so they live in the shared ``course_team_roles`` applet regardless of which applets currently provide their permissions. Their base definitions list only the permissions that ``course_team_roles`` owns, such as the legacy compatibility permissions, while every other applet adds the permissions it owns through ``role_extensions``.
20
+
21
+ Any other group of cross-cutting roles needs its own decision about where it lives.
22
+
23
+ Consequences
24
+ ************
25
+
26
+ 1. A course-team role keeps its owner when another applet starts adding permissions to it.
27
+ 2. Every role that a course team can assign is defined in one place, next to the legacy role equivalences.
28
+ 3. Base definitions such as ``course_editor`` may list no permissions, so the schema has to accept an empty permission list.
29
+
30
+ Rejected Alternatives
31
+ *********************
32
+
33
+ **Placing each course-team role in the applet that provides its permissions**
34
+ ``course_editor`` and ``course_auditor`` would move to ``course_authoring``, splitting one group of roles across applets, and each role would have to move again once another applet added permissions to it.
35
+
36
+ References
37
+ **********
38
+
39
+ * `ADR 0032`_
40
+ * `Instructor Dashboard Permissions`_
41
+
42
+ .. _ADR 0032: 0032-applets-for-built-in-authorization-capabilities.rst
43
+ .. _Instructor Dashboard Permissions: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/6789234689/Instructor+Dashboard+Permissions
@@ -116,6 +116,7 @@ test-base = [
116
116
  "coverage",
117
117
  "ddt",
118
118
  "polib",
119
+ "jsonschema",
119
120
  "pytest-cov",
120
121
  "pytest-django",
121
122
  ]
@@ -88,7 +88,6 @@ roles:
88
88
  description: Can create and edit course content, but cannot publish or change critical course settings.
89
89
  scopes:
90
90
  - course-v1
91
- hidden: true
92
91
  permissions:
93
92
  - courses.view_course
94
93
  - courses.view_course_updates
@@ -118,7 +117,6 @@ roles:
118
117
  description: Can view course content and settings, but cannot make changes.
119
118
  scopes:
120
119
  - course-v1
121
- hidden: true
122
120
  permissions:
123
121
  - courses.view_course
124
122
  - courses.view_course_updates
@@ -151,6 +151,14 @@
151
151
  "minItems": 1,
152
152
  "uniqueItems": true
153
153
  },
154
+ "role_permission_list": {
155
+ "description": "A list of unique, complete permission identifiers. The list may be empty when role extensions contribute every permission.",
156
+ "type": "array",
157
+ "items": {
158
+ "$ref": "#/$defs/permission_id"
159
+ },
160
+ "uniqueItems": true
161
+ },
154
162
  "scope_list": {
155
163
  "description": "A non-empty list of unique scope namespaces.",
156
164
  "type": "array",
@@ -240,7 +248,7 @@
240
248
  },
241
249
  "role": {
242
250
  "title": "Role",
243
- "description": "A static role and the complete set of permissions assigned to it.",
251
+ "description": "A static role definition. Permissions may be assigned by this definition or contributed by role extensions.",
244
252
  "type": "object",
245
253
  "additionalProperties": false,
246
254
  "required": ["id", "display_name", "description", "scopes", "permissions"],
@@ -269,9 +277,9 @@
269
277
  },
270
278
  "permissions": {
271
279
  "title": "Role permissions",
272
- "description": "Every permission assigned to this role. Permissions are not inferred from other permissions.",
280
+ "description": "Permissions assigned by this base definition. Role extensions may contribute the complete permission set.",
273
281
  "examples": [["courses.view_course"]],
274
- "$ref": "#/$defs/permission_list"
282
+ "$ref": "#/$defs/role_permission_list"
275
283
  },
276
284
  "icon": {
277
285
  "title": "Role icon",
@@ -3,6 +3,8 @@
3
3
  Rules come from ADR 0017 §4 and the field reference:
4
4
 
5
5
  Per-document checks:
6
+ * Roles with no base permissions are accepted with a warning because
7
+ ``role_extensions`` may contribute their complete permission set.
6
8
  * ``icon`` values, where present, are valid ``@openedx/paragon/icons`` names
7
9
  (ADR 0017 §4). The allowed set is vendored in :mod:`paragon_icons`
8
10
  (regenerate with ``make paragon_icons``; see ADR 0029).
@@ -104,6 +106,7 @@ class SchemaValidator:
104
106
 
105
107
  for role in document.roles:
106
108
  issues.extend(self._check_icon(role.icon, f"role {role.id}", sid))
109
+ issues.extend(self._check_permissions(role.id, role.permissions, sid))
107
110
 
108
111
  for extension in document.role_extensions:
109
112
  issues.extend(self._check_icon(extension.icon, f"role_extension {extension.role_id}", sid))
@@ -203,6 +206,19 @@ class SchemaValidator:
203
206
  return [ValidationIssue(IssueLevel.WARNING, f"Duplicate identical {kind} {key!r}.", sid)]
204
207
  return [ValidationIssue(IssueLevel.ERROR, f"Conflicting {kind} definition for {key!r}.", sid)]
205
208
 
209
+ @staticmethod
210
+ def _check_permissions(role_id: str, permissions: tuple[str, ...], sid: str) -> list[ValidationIssue]:
211
+ """Warn when a role relies entirely on permissions from role extensions."""
212
+ if permissions:
213
+ return []
214
+ return [
215
+ ValidationIssue(
216
+ IssueLevel.WARNING,
217
+ f"role {role_id!r} declares no base permissions; role_extensions must contribute them.",
218
+ sid,
219
+ )
220
+ ]
221
+
206
222
  @staticmethod
207
223
  def _check_role_permission_scope(
208
224
  role_id: str,
@@ -0,0 +1,48 @@
1
+ """Tests for the published authorization JSON Schema."""
2
+
3
+ import json
4
+ from importlib.resources import files
5
+
6
+ import pytest
7
+ from jsonschema import Draft202012Validator, ValidationError
8
+
9
+
10
+ @pytest.fixture(name="schema_validator", scope="module")
11
+ def fixture_schema_validator():
12
+ """Build a validator from the packaged version 1 schema."""
13
+ schema_path = files("openedx_authz.engine.schema").joinpath("authz-schema-v1.json")
14
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
15
+ return Draft202012Validator(schema)
16
+
17
+
18
+ class TestPublishedAuthorizationJsonSchema:
19
+ """The published schema enforces the supported permission-list shapes."""
20
+
21
+ def test_role_may_declare_an_empty_permission_list(self, schema_validator):
22
+ """A base role may rely entirely on role extensions for its permissions."""
23
+ contribution = {
24
+ "schema_version": "1.0",
25
+ "priority": 100,
26
+ "roles": [
27
+ {
28
+ "id": "course_staff",
29
+ "display_name": "Course Staff",
30
+ "description": "Administers a course.",
31
+ "scopes": ["course-v1"],
32
+ "permissions": [],
33
+ }
34
+ ],
35
+ }
36
+
37
+ schema_validator.validate(contribution)
38
+
39
+ def test_role_extension_permission_edits_remain_non_empty(self, schema_validator):
40
+ """An extension cannot use an empty permission edit to satisfy its change requirement."""
41
+ contribution = {
42
+ "schema_version": "1.0",
43
+ "priority": 100,
44
+ "role_extensions": [{"role_id": "course_staff", "add_permissions": []}],
45
+ }
46
+
47
+ with pytest.raises(ValidationError):
48
+ schema_validator.validate(contribution)
@@ -12,8 +12,9 @@ from unittest import mock
12
12
  from openedx_authz.engine.policy import PolicyStore
13
13
  from openedx_authz.engine.renderer import PolicyRenderer, SchemaApplier
14
14
  from openedx_authz.engine.schema.compilation import SchemaCompiler
15
+ from openedx_authz.engine.schema.validation import SchemaValidator
15
16
 
16
- from .factories import category, make_document, permission, role
17
+ from .factories import category, extension, make_document, permission, role
17
18
 
18
19
 
19
20
  class TestPolicyRendering:
@@ -68,6 +69,29 @@ class TestPolicyRendering:
68
69
  scopes = {row.scope for row in rendered.rows}
69
70
  assert scopes == {"course-v1^*", "ccx-v1^*"}
70
71
 
72
+ def test_role_extensions_supply_all_permissions_for_an_empty_base_role(self):
73
+ """An empty base role validates, compiles, and renders its extension permissions."""
74
+ base = make_document(
75
+ "roles",
76
+ categories=[category("enrollments")],
77
+ permissions=[permission(name="view_enrollments", cat="enrollments")],
78
+ roles=[role(rid="course_staff", permissions=())],
79
+ )
80
+ contribution = make_document(
81
+ "enrollments",
82
+ role_extensions=[extension("course_staff", add_permissions=("courses.view_enrollments",))],
83
+ )
84
+ validator = SchemaValidator()
85
+
86
+ assert not any(issue.is_error for issue in validator.validate([base, contribution]))
87
+ schema = SchemaCompiler().compile([base, contribution])
88
+ assert not any(issue.is_error for issue in validator.validate_compiled(schema))
89
+
90
+ rendered = PolicyRenderer().render(schema)
91
+ assert [row.as_policy() for row in rendered.rows] == [
92
+ ["role^course_staff", "act^courses.view_enrollments", "course-v1^*", "allow"]
93
+ ]
94
+
71
95
 
72
96
  class TestPolicyStoreInjection:
73
97
  """``SchemaApplier`` holds only a :class:`PolicyStore`, never the enforcer.
@@ -66,6 +66,16 @@ class TestDocumentValidation:
66
66
  messages = [i.message for i in _errors(SchemaValidator().validate([doc]))]
67
67
  assert any("unknown role" in m for m in messages)
68
68
 
69
+ def test_role_without_base_permissions_warns(self):
70
+ """An empty base permission list is accepted but reported."""
71
+ doc = make_document("roles", roles=[role(rid="course_editor", permissions=())])
72
+
73
+ issues = SchemaValidator().validate([doc])
74
+
75
+ assert not _errors(issues)
76
+ assert any("role 'course_editor' declares no base permissions" in i.message for i in _warnings(issues))
77
+ assert [i.source_id for i in _warnings(issues)] == [make_source("roles").source_id]
78
+
69
79
  def test_conflicting_duplicate_definition_is_error(self):
70
80
  """Two definitions with the same id but different content conflict."""
71
81
  doc = make_document(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openedx-authz
3
- Version: 2.3.0
3
+ Version: 2.5.0
4
4
  Summary: Open edX AuthZ provides the architecture and foundations of the authorization framework.
5
5
  Author-email: Open edX Project <oscm@openedx.org>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -73,6 +73,8 @@ docs/decisions/0028-api-contract-for-role-catalog.rst
73
73
  docs/decisions/0029-paragon-icon-list-maintenance.rst
74
74
  docs/decisions/0030-tutor-implementation-authz-schema-discovery.rst
75
75
  docs/decisions/0031-priority-applies-to-role-extensions-only.rst
76
+ docs/decisions/0032-applets-for-built-in-authorization-capabilities.rst
77
+ docs/decisions/0033-shared-applet-for-course-team-roles.rst
76
78
  docs/decisions/README.rst
77
79
  docs/how-tos/index.rst
78
80
  docs/quickstarts/index.rst
@@ -244,6 +246,7 @@ src/openedx_authz/tests/schema/factories.py
244
246
  src/openedx_authz/tests/schema/test_apply.py
245
247
  src/openedx_authz/tests/schema/test_compilation.py
246
248
  src/openedx_authz/tests/schema/test_discovery.py
249
+ src/openedx_authz/tests/schema/test_json_schema.py
247
250
  src/openedx_authz/tests/schema/test_load_authz_schema_command.py
248
251
  src/openedx_authz/tests/schema/test_loading.py
249
252
  src/openedx_authz/tests/schema/test_parsing.py
@@ -62,6 +62,8 @@
62
62
  "docs/decisions/0029-paragon-icon-list-maintenance.rst",
63
63
  "docs/decisions/0030-tutor-implementation-authz-schema-discovery.rst",
64
64
  "docs/decisions/0031-priority-applies-to-role-extensions-only.rst",
65
+ "docs/decisions/0032-applets-for-built-in-authorization-capabilities.rst",
66
+ "docs/decisions/0033-shared-applet-for-course-team-roles.rst",
65
67
  "docs/decisions/README.rst",
66
68
  "docs/getting_started.rst",
67
69
  "docs/how-tos/index.rst",
@@ -224,6 +226,7 @@
224
226
  "src/openedx_authz/tests/schema/test_apply.py",
225
227
  "src/openedx_authz/tests/schema/test_compilation.py",
226
228
  "src/openedx_authz/tests/schema/test_discovery.py",
229
+ "src/openedx_authz/tests/schema/test_json_schema.py",
227
230
  "src/openedx_authz/tests/schema/test_load_authz_schema_command.py",
228
231
  "src/openedx_authz/tests/schema/test_loading.py",
229
232
  "src/openedx_authz/tests/schema/test_parsing.py",
@@ -1553,6 +1553,7 @@ dev = [
1553
1553
  { name = "doc8" },
1554
1554
  { name = "edx-i18n-tools" },
1555
1555
  { name = "edx-lint" },
1556
+ { name = "jsonschema" },
1556
1557
  { name = "polib" },
1557
1558
  { name = "pydocstyle" },
1558
1559
  { name = "pytest-cov" },
@@ -1573,6 +1574,7 @@ doc = [
1573
1574
  { name = "ddt" },
1574
1575
  { name = "django" },
1575
1576
  { name = "doc8" },
1577
+ { name = "jsonschema" },
1576
1578
  { name = "polib" },
1577
1579
  { name = "pytest-cov" },
1578
1580
  { name = "pytest-django" },
@@ -1588,6 +1590,7 @@ quality = [
1588
1590
  { name = "ddt" },
1589
1591
  { name = "django" },
1590
1592
  { name = "edx-lint" },
1593
+ { name = "jsonschema" },
1591
1594
  { name = "polib" },
1592
1595
  { name = "pydocstyle" },
1593
1596
  { name = "pytest-cov" },
@@ -1599,6 +1602,7 @@ test = [
1599
1602
  { name = "coverage" },
1600
1603
  { name = "ddt" },
1601
1604
  { name = "django" },
1605
+ { name = "jsonschema" },
1602
1606
  { name = "polib" },
1603
1607
  { name = "pytest-cov" },
1604
1608
  { name = "pytest-django" },
@@ -1607,6 +1611,7 @@ test-base = [
1607
1611
  { name = "code-annotations" },
1608
1612
  { name = "coverage" },
1609
1613
  { name = "ddt" },
1614
+ { name = "jsonschema" },
1610
1615
  { name = "polib" },
1611
1616
  { name = "pytest-cov" },
1612
1617
  { name = "pytest-django" },
@@ -1648,6 +1653,7 @@ dev = [
1648
1653
  { name = "doc8" },
1649
1654
  { name = "edx-i18n-tools" },
1650
1655
  { name = "edx-lint" },
1656
+ { name = "jsonschema" },
1651
1657
  { name = "polib" },
1652
1658
  { name = "pydocstyle" },
1653
1659
  { name = "pytest-cov" },
@@ -1667,6 +1673,7 @@ doc = [
1667
1673
  { name = "ddt" },
1668
1674
  { name = "django", specifier = ">=5.2,<6.0" },
1669
1675
  { name = "doc8" },
1676
+ { name = "jsonschema" },
1670
1677
  { name = "polib" },
1671
1678
  { name = "pytest-cov" },
1672
1679
  { name = "pytest-django" },
@@ -1681,6 +1688,7 @@ quality = [
1681
1688
  { name = "ddt" },
1682
1689
  { name = "django", specifier = ">=5.2,<6.0" },
1683
1690
  { name = "edx-lint" },
1691
+ { name = "jsonschema" },
1684
1692
  { name = "polib" },
1685
1693
  { name = "pydocstyle" },
1686
1694
  { name = "pytest-cov" },
@@ -1692,6 +1700,7 @@ test = [
1692
1700
  { name = "coverage" },
1693
1701
  { name = "ddt" },
1694
1702
  { name = "django", specifier = ">=5.2,<6.0" },
1703
+ { name = "jsonschema" },
1695
1704
  { name = "polib" },
1696
1705
  { name = "pytest-cov" },
1697
1706
  { name = "pytest-django" },
@@ -1700,6 +1709,7 @@ test-base = [
1700
1709
  { name = "code-annotations" },
1701
1710
  { name = "coverage" },
1702
1711
  { name = "ddt" },
1712
+ { name = "jsonschema" },
1703
1713
  { name = "polib" },
1704
1714
  { name = "pytest-cov" },
1705
1715
  { name = "pytest-django" },
File without changes
File without changes