openedx-authz 2.1.0__tar.gz → 2.2.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.
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/ci.yml +1 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/release.yml +1 -1
- {openedx_authz-2.1.0/src/openedx_authz.egg-info → openedx_authz-2.2.0}/PKG-INFO +1 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0030-tutor-implementation-authz-schema-discovery.rst +1 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/discovery.py +91 -2
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/parsing.py +28 -17
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/__init__.py +2 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/loading.py +16 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_discovery.py +133 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_loading.py +167 -5
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_parsing.py +0 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0/src/openedx_authz.egg-info}/PKG-INFO +1 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/SOURCES.txt +12 -1
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/scm_file_list.json +11 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/.gitignore +7 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/LICENSE.txt +662 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/MANIFEST.in +2 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/Makefile +30 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/README.rst +80 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/pyproject.toml +65 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/tutor_authz/__about__.py +1 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/tutor_authz/__init__.py +0 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/tutor_authz/patches/openedx-common-settings +2 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/tutor_authz/plugin.py +222 -0
- openedx_authz-2.2.0/tutor/tutor-contrib-authz/tutor_authz/templates/apps/openedx/config/authz-schema.yml +9 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.annotation_safe_list.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.editorconfig +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/dependabot.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/add-depr-ticket-to-depr-board.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/add-remove-label-on-comment.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/commitlint.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/self-assign-issue.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.github/workflows/upgrade-python-requirements.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.gitignore +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.pii_annotations.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/.readthedocs.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/CHANGELOG.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/LICENSE +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/MANIFEST.in +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/Makefile +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/README.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/catalog-info.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/codecov.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/Makefile +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/_images/architecture-overview.png +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/_static/theme_overrides.css +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/changelog.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/concepts/core_roles_and_permissions/content_library_roles.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/concepts/core_roles_and_permissions/course_roles.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/concepts/core_roles_and_permissions/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/concepts/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/conf.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0001-purpose-of-this-repo.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0002-authorization-model-foundation.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0003-jwt-usage.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0004-technology-selection.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0005-architecture-and-data-modeling.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0007-enforcement-mechanisms-mfe.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0008-compatibility-scheme.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0009-authz-for-course-authoring.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0010-course-authoring-flag.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0011-course-authoring-migration-process.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0012-auditability.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0012-glob-support-for-role-assignments.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0013-course-authoring-automatic-migration.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0014-bulk-assignment-queries-without-casbin-enforce.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0015-expose-course-authoring-waffle-flag-state-via-rest-api.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0016-static-and-dynamic-roles.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0017-static-authorization-schema.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0018-authorization-schema-lifecycle.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0019-authorization-schema-discovery.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0020-authorization-schema-internationalization.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0021-authorization-definition-api.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0022-defer-authorization-implications.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0023-extend-static-roles.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0024-api-contract-for-user-grouped-role-assignments.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0025-authorization-schema-source-tracking.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0026-rest-api-domain-ownership-boundary.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0027-cross-domain-filtering-via-openedx-filters.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0028-api-contract-for-role-catalog.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0029-paragon-icon-list-maintenance.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/0031-priority-applies-to-role-extensions-only.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions/README.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/decisions.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/getting_started.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/how-tos/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/internationalization.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/make.bat +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/quickstarts/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/readme.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/references/authorization-schema-json.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/references/authorization-schema.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/references/index.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/docs/testing.rst +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/manage.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/pylintrc +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/pylintrc_tweaks +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/pyproject.toml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/ruff.toml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/scripts/generate_paragon_icons.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/setup.cfg +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/admin.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/catalog.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/data.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/permissions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/roles.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/users.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/api/utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/apps.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/authz/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/authz/schema/course_permissions.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/authz/schema/course_roles.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/authz/schema/library_permissions.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/authz/schema/library_roles.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/conf/locale/config.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/constants/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/constants/permissions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/constants/roles.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/constants/schema.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/data.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/adapter.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/apps.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/config/authz.policy +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/config/model.conf +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/enforcer.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/filter.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/matcher.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/policy.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/renderer.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/authz-schema-v1.json +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/compilation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/exceptions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/issues.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/loading.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/paragon_icons.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/pipeline.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/syntax.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/translation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/compilation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/definitions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/validation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/filters.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/handlers.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/authz_migrate_course_authoring.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/authz_rollback_course_authoring.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/enforcement.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/extract_schema_translations.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/load_authz_schema.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/management/commands/load_policies.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0001_add_casbin_dependency.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0002_initial.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0003_usersubject.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0004_contentlibraryscope.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0005_policycachecontrol.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0006_migrate_legacy_permissions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0007_coursescope.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0008_authzcourseauthoringmigrationrun.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0009_roleassignmentaudit.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0010_scope_external_key.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/0011_authz_schema_definitions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/migrations/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/authz_migration.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/base.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/core.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/engine.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/schema.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/scopes.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/models/subjects.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/data.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/decorators.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/urls.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/admin_console/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/admin_console/filters.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/admin_console/serializers.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/admin_console/views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/course_authoring/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/course_authoring/pipeline.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/course_authoring/views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/fields.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/paginators.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/permissions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/serializers.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/urls.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/rest_api/v1/views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/settings/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/settings/common.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/settings/production.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/settings/test.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/templates/openedx_authz/base.html +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/api/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/api/test_catalog.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/api/test_data.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/api/test_roles.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/api/test_users.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/authz/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/authz/test_resources.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/conftest.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/categories.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/extensions.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/permissions.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_compiled/roles.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_parse/malformed.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_semantics/unknown_category.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_structure/items.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/invalid_structure/priority.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/categories.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/extensions.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/permissions.yml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/fixtures/schema_pipeline/valid/roles.yaml +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/test_handlers.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/test_models.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/test_schema_apply.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/test_schema_pipeline.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/integration/test_views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/admin_console/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/admin_console/test_views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/course_authoring/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/course_authoring/test_pipeline.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/course_authoring/test_views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/mixins.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/test_permissions.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/test_utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/rest_api/test_views.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/factories.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_apply.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_compilation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_load_authz_schema_command.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_pipeline.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_renderer.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_schema_models.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_source_storage.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_syntax.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_translation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_types.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_validation.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/__init__.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/apps.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/migrations/0001_initial.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/migrations/0002_learningpackage_contentlibrary_learning_package.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/migrations/0002_userprofile.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/stubs/models.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_commands.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_enforcement.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_enforcer.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_engine_utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_filter.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_handlers.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_imports.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_migrations.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_models.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_policy.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/test_utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/urls.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/utils.py +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/dependency_links.txt +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/entry_points.txt +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/requires.txt +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz.egg-info/top_level.txt +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/tox.ini +0 -0
- {openedx_authz-2.1.0 → openedx_authz-2.2.0}/uv.lock +0 -0
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
22
|
|
|
23
23
|
- name: Install uv
|
|
24
|
-
uses: astral-sh/setup-uv@
|
|
24
|
+
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
|
|
25
25
|
with:
|
|
26
26
|
enable-cache: true
|
|
27
27
|
python-version: ${{ matrix.python-version }}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openedx-authz
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -102,7 +102,7 @@ Example rendered file with two plugin contributions:
|
|
|
102
102
|
Schemas discovered through ``OPENEDX_AUTHZ_SCHEMA_FILES`` have no Python package or module. To satisfy the source-tracking requirements of `ADR 0025`_, their source record uses:
|
|
103
103
|
|
|
104
104
|
* ``distribution`` = ``<tutor-patch>`` — a sentinel value that is an invalid Python package name, guaranteeing no collision with any installed distribution.
|
|
105
|
-
* ``module`` =
|
|
105
|
+
* ``module`` = the absolute filesystem path of the schema file (e.g. ``/openedx/config/authz-schema.yml``). Filesystem sources have no owning Python module, but reusing the path here keeps the ``(distribution, module)`` unique constraint enforceable and avoids the ``NULL``-handling complications that a ``null`` value would introduce across the ORM, migrations, and source-identity logic.
|
|
106
106
|
* ``resource_path`` = the absolute filesystem path of the schema file (e.g. ``/openedx/config/authz-schema.yml``).
|
|
107
107
|
* ``distribution_version`` = ``0.0.0`` — a sentinel value, since filesystem sources carry no package version.
|
|
108
108
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Discover static authz schema resources (the ``discover`` step, ADR 0019).
|
|
2
2
|
|
|
3
|
-
Providers register **directories**
|
|
4
|
-
|
|
3
|
+
Providers register **directories** of ``.yaml`` files, or individual absolute
|
|
4
|
+
filesystem paths. Three contribution sources are merged:
|
|
5
5
|
|
|
6
6
|
1. The ``authz.schema`` entry-point group. Each registered callable returns
|
|
7
7
|
directory paths in the package-anchored format below (e.g.
|
|
@@ -9,6 +9,10 @@ every ``.yaml`` file inside them. Two contribution sources are merged:
|
|
|
9
9
|
2. The ``OPENEDX_AUTHZ_SCHEMA_DIRECTORIES`` Django setting, a list of directory
|
|
10
10
|
path strings in the same format. This lets operators and CI contribute
|
|
11
11
|
directories without shipping a package entry point.
|
|
12
|
+
3. The ``OPENEDX_AUTHZ_SCHEMA_FILES`` Django setting, a list of absolute
|
|
13
|
+
filesystem paths each naming one schema file (ADR 0030). This is the route
|
|
14
|
+
used by the Tutor ``openedx-authz-schema`` patch to feed a rendered file
|
|
15
|
+
into discovery without anchoring it on a Python package.
|
|
12
16
|
|
|
13
17
|
Directory format: these are **not** filesystem paths (neither relative nor
|
|
14
18
|
absolute). Each is a package-anchored ``importlib.resources`` path: the first
|
|
@@ -36,12 +40,15 @@ import logging
|
|
|
36
40
|
from dataclasses import dataclass
|
|
37
41
|
from enum import StrEnum
|
|
38
42
|
from importlib import metadata, resources
|
|
43
|
+
from pathlib import Path
|
|
39
44
|
|
|
40
45
|
from django.conf import settings
|
|
41
46
|
from django.core.exceptions import ImproperlyConfigured
|
|
42
47
|
|
|
43
48
|
ENTRY_POINT_GROUP = "authz.schema"
|
|
44
49
|
SETTINGS_DIRECTORIES_NAME = "OPENEDX_AUTHZ_SCHEMA_DIRECTORIES"
|
|
50
|
+
SETTINGS_FILE_SENTINEL = "<tutor-patch>"
|
|
51
|
+
SETTINGS_FILE_VERSION = "0.0.0"
|
|
45
52
|
SCHEMA_FILE_SUFFIXES = (".yaml", ".yml")
|
|
46
53
|
|
|
47
54
|
logger = logging.getLogger(__name__)
|
|
@@ -101,6 +108,45 @@ class DiscoveredResource:
|
|
|
101
108
|
) from exc
|
|
102
109
|
|
|
103
110
|
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class DiscoveredFileResource(DiscoveredResource):
|
|
113
|
+
"""A schema file discovered via an absolute filesystem path (ADR 0030).
|
|
114
|
+
|
|
115
|
+
Unlike :class:`DiscoveredResource`, which resolves through
|
|
116
|
+
``importlib.resources``, this variant reads directly from the filesystem.
|
|
117
|
+
Fields are populated with sentinel values: ``package`` is
|
|
118
|
+
:data:`SETTINGS_FILE_SENTINEL` and both ``module`` and ``resource_path``
|
|
119
|
+
hold the absolute path.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_path(cls, file_path: str) -> DiscoveredFileResource:
|
|
124
|
+
"""Build a resource from an absolute filesystem path.
|
|
125
|
+
|
|
126
|
+
Sentinel values are used for ``package`` and ``module`` because the
|
|
127
|
+
file is not anchored on any Python package.
|
|
128
|
+
"""
|
|
129
|
+
return cls(
|
|
130
|
+
package=SETTINGS_FILE_SENTINEL,
|
|
131
|
+
resource_path=file_path,
|
|
132
|
+
module=file_path,
|
|
133
|
+
origin=Origin.SETTINGS,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def read_bytes(self) -> bytes:
|
|
137
|
+
"""Read the file directly from the filesystem via :class:`pathlib.Path`.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
SchemaDiscoveryError: If the file cannot be read.
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
return Path(self.resource_path).read_bytes()
|
|
144
|
+
except OSError as exc:
|
|
145
|
+
raise SchemaDiscoveryError(
|
|
146
|
+
f"Could not read schema file {self.resource_path!r}: {exc}"
|
|
147
|
+
) from exc
|
|
148
|
+
|
|
149
|
+
|
|
104
150
|
class SchemaDiscoveryError(Exception):
|
|
105
151
|
"""Raised when a provider fails or a declared directory cannot be read."""
|
|
106
152
|
|
|
@@ -139,6 +185,7 @@ class SchemaDiscovery:
|
|
|
139
185
|
found: list[DiscoveredResource] = []
|
|
140
186
|
found.extend(self._discover_entry_points())
|
|
141
187
|
found.extend(self._discover_settings_directories())
|
|
188
|
+
found.extend(self._discover_settings_files())
|
|
142
189
|
for directory in self._passed_in_directories:
|
|
143
190
|
found.extend(self._iter_directory(directory, origin=Origin.PASSED_IN))
|
|
144
191
|
|
|
@@ -184,6 +231,48 @@ class SchemaDiscovery:
|
|
|
184
231
|
discovered.extend(self._iter_directory(directory, origin=Origin.SETTINGS))
|
|
185
232
|
return discovered
|
|
186
233
|
|
|
234
|
+
def _discover_settings_files(self) -> list[DiscoveredResource]:
|
|
235
|
+
"""Read ``OPENEDX_AUTHZ_SCHEMA_FILES`` from Django settings (ADR 0030).
|
|
236
|
+
|
|
237
|
+
Each item is an absolute filesystem path to a single schema file.
|
|
238
|
+
Unlike ``_discover_settings_directories``, every listed path must exist
|
|
239
|
+
and have a supported extension; a missing or misnamed file is treated
|
|
240
|
+
as a misconfiguration and raises ``SchemaDiscoveryError``.
|
|
241
|
+
|
|
242
|
+
Absent, empty, or unconfigured settings yield nothing. As with
|
|
243
|
+
``_discover_settings_directories``, ``ImproperlyConfigured`` degrades
|
|
244
|
+
to no contribution so CI can run outside a Django process.
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
file_paths = getattr(settings, "OPENEDX_AUTHZ_SCHEMA_FILES", None) or []
|
|
248
|
+
except ImproperlyConfigured:
|
|
249
|
+
return []
|
|
250
|
+
if not isinstance(file_paths, list):
|
|
251
|
+
raise SchemaDiscoveryError(
|
|
252
|
+
"OPENEDX_AUTHZ_SCHEMA_FILES must be a list of filesystem paths, "
|
|
253
|
+
f"got {type(file_paths).__name__!r}."
|
|
254
|
+
)
|
|
255
|
+
discovered: list[DiscoveredResource] = []
|
|
256
|
+
for file_path in file_paths:
|
|
257
|
+
if not file_path.endswith(SCHEMA_FILE_SUFFIXES):
|
|
258
|
+
raise SchemaDiscoveryError(
|
|
259
|
+
f"Schema file {file_path!r} contributed through "
|
|
260
|
+
"OPENEDX_AUTHZ_SCHEMA_FILES has an unsupported file extension, "
|
|
261
|
+
f"accepted extensions: {SCHEMA_FILE_SUFFIXES}"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
path = Path(file_path)
|
|
265
|
+
if not path.is_file():
|
|
266
|
+
raise SchemaDiscoveryError(
|
|
267
|
+
f"Schema file {file_path!r} contributed through "
|
|
268
|
+
"OPENEDX_AUTHZ_SCHEMA_FILES is not an actual file"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
discovered.append(
|
|
272
|
+
DiscoveredFileResource.from_path(file_path)
|
|
273
|
+
)
|
|
274
|
+
return discovered
|
|
275
|
+
|
|
187
276
|
def _iter_directory(self, directory: str, *, origin: Origin) -> list[DiscoveredResource]:
|
|
188
277
|
"""Resolve a directory path and return one resource per ``.yaml`` file.
|
|
189
278
|
|
|
@@ -17,9 +17,13 @@ from importlib import metadata
|
|
|
17
17
|
|
|
18
18
|
import yaml
|
|
19
19
|
|
|
20
|
-
from openedx_authz.engine.schema.discovery import
|
|
20
|
+
from openedx_authz.engine.schema.discovery import (
|
|
21
|
+
SETTINGS_FILE_VERSION,
|
|
22
|
+
DiscoveredFileResource,
|
|
23
|
+
DiscoveredResource,
|
|
24
|
+
)
|
|
21
25
|
from openedx_authz.engine.schema.exceptions import SchemaLoadError
|
|
22
|
-
from openedx_authz.engine.schema.types import ParsedSchemaDocument, SourceRecord
|
|
26
|
+
from openedx_authz.engine.schema.types import ParsedSchemaDocument, SourceFileRecord, SourceRecord
|
|
23
27
|
|
|
24
28
|
logger = logging.getLogger(__name__)
|
|
25
29
|
|
|
@@ -42,17 +46,19 @@ class SchemaParser:
|
|
|
42
46
|
documents = []
|
|
43
47
|
for resource in resources:
|
|
44
48
|
contents = resource.read_bytes()
|
|
45
|
-
data
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
for data in self.parse_yamls(contents, resource):
|
|
50
|
+
schema_version = str(data.get("schema_version", "")) if isinstance(data, dict) else ""
|
|
51
|
+
source = self.build_source_record(resource, contents, schema_version)
|
|
52
|
+
documents.append(ParsedSchemaDocument(source=source, data=data))
|
|
49
53
|
return documents
|
|
50
54
|
|
|
51
55
|
@staticmethod
|
|
52
|
-
def
|
|
53
|
-
"""Parse YAML bytes
|
|
56
|
+
def parse_yamls(contents: bytes, resource: DiscoveredResource) -> list[object]:
|
|
57
|
+
"""Parse YAML bytes as a multi-document stream, skipping empty
|
|
58
|
+
documents, and preserve the resulting root value.
|
|
59
|
+
"""
|
|
54
60
|
try:
|
|
55
|
-
return yaml.
|
|
61
|
+
return [doc for doc in yaml.safe_load_all(contents) if doc is not None]
|
|
56
62
|
except yaml.YAMLError as exc:
|
|
57
63
|
raise SchemaLoadError(f"Invalid YAML in {resource.package}:{resource.resource_path}: {exc}") from exc
|
|
58
64
|
|
|
@@ -66,14 +72,17 @@ class SchemaParser:
|
|
|
66
72
|
"""
|
|
67
73
|
distribution, version = self.resolve_distribution(resource)
|
|
68
74
|
content_digest = hashlib.sha256(contents).hexdigest()
|
|
69
|
-
|
|
70
|
-
distribution
|
|
71
|
-
distribution_version
|
|
72
|
-
module
|
|
73
|
-
resource_path
|
|
74
|
-
schema_version
|
|
75
|
-
content_digest
|
|
76
|
-
|
|
75
|
+
source_record = {
|
|
76
|
+
'distribution': distribution,
|
|
77
|
+
'distribution_version': version,
|
|
78
|
+
'module': resource.module,
|
|
79
|
+
'resource_path': resource.resource_path,
|
|
80
|
+
'schema_version': schema_version,
|
|
81
|
+
'content_digest': content_digest,
|
|
82
|
+
}
|
|
83
|
+
if isinstance(resource, DiscoveredFileResource):
|
|
84
|
+
return SourceFileRecord(**source_record)
|
|
85
|
+
return SourceRecord(**source_record)
|
|
77
86
|
|
|
78
87
|
@classmethod
|
|
79
88
|
def resolve_distribution(cls, resource: DiscoveredResource) -> tuple[str, str]:
|
|
@@ -88,6 +97,8 @@ class SchemaParser:
|
|
|
88
97
|
the one that actually ships this resource's file, and only fall back to
|
|
89
98
|
a deterministic (sorted) choice when no single owner can be identified.
|
|
90
99
|
"""
|
|
100
|
+
if isinstance(resource, DiscoveredFileResource):
|
|
101
|
+
return resource.package, SETTINGS_FILE_VERSION
|
|
91
102
|
top_level = resource.package.split(".", 1)[0]
|
|
92
103
|
try:
|
|
93
104
|
mapping = metadata.packages_distributions()
|
{openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/__init__.py
RENAMED
|
@@ -37,6 +37,7 @@ from openedx_authz.engine.schema.types.definitions import (
|
|
|
37
37
|
from openedx_authz.engine.schema.types.loading import (
|
|
38
38
|
ParsedSchemaDocument,
|
|
39
39
|
SchemaDocument,
|
|
40
|
+
SourceFileRecord,
|
|
40
41
|
SourceRecord,
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -49,6 +50,7 @@ __all__ = [
|
|
|
49
50
|
# loading
|
|
50
51
|
"SourceRecord",
|
|
51
52
|
"ParsedSchemaDocument",
|
|
53
|
+
"SourceFileRecord",
|
|
52
54
|
"SchemaDocument",
|
|
53
55
|
# compilation
|
|
54
56
|
"RelationshipSource",
|
{openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/engine/schema/types/loading.py
RENAMED
|
@@ -60,6 +60,22 @@ class SourceRecord:
|
|
|
60
60
|
return f"{self.distribution}:{module_path}/{filename}"
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class SourceFileRecord(SourceRecord):
|
|
65
|
+
"""Source record for a filesystem-path schema resource (ADR 0030).
|
|
66
|
+
|
|
67
|
+
Overrides :attr:`source_id` to use the absolute path directly, since the
|
|
68
|
+
parent's ``module.replace(".", "/")`` convention assumes dotted Python
|
|
69
|
+
module paths and would corrupt a filesystem path containing a file
|
|
70
|
+
extension.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def source_id(self) -> str:
|
|
75
|
+
"""Return ``"<distribution>:<absolute_path>"``."""
|
|
76
|
+
return f"{self.distribution}:{self.resource_path}"
|
|
77
|
+
|
|
78
|
+
|
|
63
79
|
@dataclass(frozen=True)
|
|
64
80
|
class ParsedSchemaDocument:
|
|
65
81
|
"""One parsed YAML value and the provenance of the resource that supplied it."""
|
{openedx_authz-2.1.0 → openedx_authz-2.2.0}/src/openedx_authz/tests/schema/test_discovery.py
RENAMED
|
@@ -5,6 +5,7 @@ contract of :class:`SchemaDiscovery`:
|
|
|
5
5
|
|
|
6
6
|
* ``TestDirectoryExpansion`` — a contributed directory expands to its YAML files.
|
|
7
7
|
* ``TestSettingsDirectories`` — the operator/Tutor settings contribution route.
|
|
8
|
+
* ``TestSettingsFiles`` — the filesystem-path contribution route (ADR 0030).
|
|
8
9
|
* ``TestDiscoverySourcePrecedence`` — how sources merge and de-duplicate.
|
|
9
10
|
* ``TestDiscoveryErrors`` — every failure surfaces as ``SchemaDiscoveryError``.
|
|
10
11
|
* ``TestSchemaFileSelection`` — only YAML *files* are picked up.
|
|
@@ -15,6 +16,8 @@ import pytest
|
|
|
15
16
|
from django.test import override_settings
|
|
16
17
|
|
|
17
18
|
from openedx_authz.engine.schema.discovery import (
|
|
19
|
+
SETTINGS_FILE_SENTINEL,
|
|
20
|
+
DiscoveredFileResource,
|
|
18
21
|
DiscoveredResource,
|
|
19
22
|
Origin,
|
|
20
23
|
SchemaDiscovery,
|
|
@@ -159,6 +162,136 @@ class TestSettingsDirectories:
|
|
|
159
162
|
assert not SchemaDiscovery()._discover_settings_directories()
|
|
160
163
|
|
|
161
164
|
|
|
165
|
+
class TestSettingsFiles:
|
|
166
|
+
"""The filesystem-path contribution route (ADR 0030).
|
|
167
|
+
|
|
168
|
+
Tutor renders patch content into a YAML file and lists its absolute path
|
|
169
|
+
in ``OPENEDX_AUTHZ_SCHEMA_FILES``. These tests exercise
|
|
170
|
+
``_discover_settings_files`` and ``DiscoveredFileResource`` in isolation.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
@override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=["/tmp/nonexistent/authz-schema.yaml"])
|
|
174
|
+
def test_nonexistent_file_raises(self):
|
|
175
|
+
"""An explicitly listed file that does not exist is a misconfiguration."""
|
|
176
|
+
with pytest.raises(SchemaDiscoveryError, match="is not an actual file"):
|
|
177
|
+
SchemaDiscovery().discover()
|
|
178
|
+
|
|
179
|
+
@override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=["/tmp/authz-schema.txt"])
|
|
180
|
+
def test_unsupported_extension_raises(self):
|
|
181
|
+
"""A file with an unsupported extension is rejected even if it exists."""
|
|
182
|
+
with pytest.raises(SchemaDiscoveryError, match="unsupported file extension"):
|
|
183
|
+
SchemaDiscovery().discover()
|
|
184
|
+
|
|
185
|
+
def test_settings_file_is_discovered(self, tmp_path):
|
|
186
|
+
"""A valid YAML file listed in the setting is discovered and readable."""
|
|
187
|
+
schema_file = tmp_path / "authz-schema.yaml"
|
|
188
|
+
schema_file.write_text("schema_version: '1.0'\npriority: 100\n", encoding="utf-8")
|
|
189
|
+
|
|
190
|
+
with self._override_files([str(schema_file)]):
|
|
191
|
+
resources = SchemaDiscovery().discover()
|
|
192
|
+
|
|
193
|
+
file_resources = [r for r in resources if isinstance(r, DiscoveredFileResource)]
|
|
194
|
+
assert len(file_resources) == 1
|
|
195
|
+
assert file_resources[0].resource_path == str(schema_file)
|
|
196
|
+
assert file_resources[0].package == SETTINGS_FILE_SENTINEL
|
|
197
|
+
assert file_resources[0].origin == Origin.SETTINGS
|
|
198
|
+
|
|
199
|
+
def test_contents_are_readable(self, tmp_path):
|
|
200
|
+
"""A discovered file resource returns the file's bytes."""
|
|
201
|
+
schema_file = tmp_path / "roles.yml"
|
|
202
|
+
schema_file.write_text("schema_version: '1.0'\n", encoding="utf-8")
|
|
203
|
+
|
|
204
|
+
with self._override_files([str(schema_file)]):
|
|
205
|
+
resources = SchemaDiscovery().discover()
|
|
206
|
+
|
|
207
|
+
file_resources = [r for r in resources if isinstance(r, DiscoveredFileResource)]
|
|
208
|
+
assert b"schema_version" in file_resources[0].read_bytes()
|
|
209
|
+
|
|
210
|
+
def test_multiple_files_are_discovered(self, tmp_path):
|
|
211
|
+
"""Multiple listed files are all discovered."""
|
|
212
|
+
file_a = tmp_path / "a.yaml"
|
|
213
|
+
file_b = tmp_path / "b.yml"
|
|
214
|
+
file_a.write_text("schema_version: '1.0'\n", encoding="utf-8")
|
|
215
|
+
file_b.write_text("schema_version: '1.0'\n", encoding="utf-8")
|
|
216
|
+
|
|
217
|
+
with self._override_files([str(file_a), str(file_b)]):
|
|
218
|
+
resources = SchemaDiscovery().discover()
|
|
219
|
+
|
|
220
|
+
file_resources = [r for r in resources if isinstance(r, DiscoveredFileResource)]
|
|
221
|
+
assert len(file_resources) == 2
|
|
222
|
+
|
|
223
|
+
def test_yml_and_yaml_extensions_are_accepted(self, tmp_path):
|
|
224
|
+
"""Both ``.yml`` and ``.yaml`` suffixes are valid."""
|
|
225
|
+
for name in ("a.yml", "b.yaml"):
|
|
226
|
+
(tmp_path / name).write_text("schema_version: '1.0'\n", encoding="utf-8")
|
|
227
|
+
|
|
228
|
+
with self._override_files([str(tmp_path / "a.yml"), str(tmp_path / "b.yaml")]):
|
|
229
|
+
resources = SchemaDiscovery().discover()
|
|
230
|
+
|
|
231
|
+
file_resources = [r for r in resources if isinstance(r, DiscoveredFileResource)]
|
|
232
|
+
assert len(file_resources) == 2
|
|
233
|
+
|
|
234
|
+
def test_absent_setting_contributes_nothing(self):
|
|
235
|
+
"""The default state: no deployment has declared the setting at all."""
|
|
236
|
+
# pylint: disable=protected-access
|
|
237
|
+
assert not SchemaDiscovery()._discover_settings_files()
|
|
238
|
+
|
|
239
|
+
@override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=[])
|
|
240
|
+
def test_empty_setting_contributes_nothing(self):
|
|
241
|
+
"""An empty list is a no-op, not an error."""
|
|
242
|
+
# pylint: disable=protected-access
|
|
243
|
+
assert not SchemaDiscovery()._discover_settings_files()
|
|
244
|
+
|
|
245
|
+
@override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=None)
|
|
246
|
+
def test_none_setting_contributes_nothing(self):
|
|
247
|
+
"""An explicit ``None`` degrades to no contribution."""
|
|
248
|
+
# pylint: disable=protected-access
|
|
249
|
+
assert not SchemaDiscovery()._discover_settings_files()
|
|
250
|
+
|
|
251
|
+
@override_settings(OPENEDX_AUTHZ_SCHEMA_FILES="/etc/authz/schema.yaml")
|
|
252
|
+
def test_non_list_setting_raises(self):
|
|
253
|
+
"""A bare string instead of a list is rejected as a misconfiguration."""
|
|
254
|
+
with pytest.raises(SchemaDiscoveryError, match="must be a list"):
|
|
255
|
+
SchemaDiscovery().discover()
|
|
256
|
+
|
|
257
|
+
def test_unconfigured_django_contributes_nothing(self, monkeypatch):
|
|
258
|
+
"""``ImproperlyConfigured`` degrades to no contribution."""
|
|
259
|
+
# pylint: disable=import-outside-toplevel
|
|
260
|
+
from django.conf import LazySettings
|
|
261
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
262
|
+
|
|
263
|
+
def _unconfigured(_self, name):
|
|
264
|
+
raise ImproperlyConfigured(f"Requested setting {name}, but settings are not configured")
|
|
265
|
+
|
|
266
|
+
monkeypatch.setattr(LazySettings, "__getattr__", _unconfigured)
|
|
267
|
+
|
|
268
|
+
# pylint: disable=protected-access
|
|
269
|
+
assert not SchemaDiscovery()._discover_settings_files()
|
|
270
|
+
|
|
271
|
+
def test_file_resource_is_marked_with_settings_origin(self, tmp_path):
|
|
272
|
+
"""``origin`` identifies the settings route."""
|
|
273
|
+
schema_file = tmp_path / "authz.yaml"
|
|
274
|
+
schema_file.write_text("schema_version: '1.0'\n", encoding="utf-8")
|
|
275
|
+
|
|
276
|
+
with self._override_files([str(schema_file)]):
|
|
277
|
+
# pylint: disable=protected-access
|
|
278
|
+
resources = SchemaDiscovery()._discover_settings_files()
|
|
279
|
+
|
|
280
|
+
assert {r.origin for r in resources} == {Origin.SETTINGS}
|
|
281
|
+
|
|
282
|
+
def test_unreadable_file_resource_raises(self, tmp_path):
|
|
283
|
+
"""Reading a file that was removed after discovery raises ``SchemaDiscoveryError``."""
|
|
284
|
+
resource = DiscoveredFileResource.from_path(str(tmp_path / "gone.yaml"))
|
|
285
|
+
|
|
286
|
+
with pytest.raises(SchemaDiscoveryError, match="Could not read schema file"):
|
|
287
|
+
resource.read_bytes()
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _override_files(paths):
|
|
291
|
+
"""Convenience wrapper for ``override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=...)``."""
|
|
292
|
+
return override_settings(OPENEDX_AUTHZ_SCHEMA_FILES=paths)
|
|
293
|
+
|
|
294
|
+
|
|
162
295
|
class TestDiscoverySourcePrecedence:
|
|
163
296
|
"""Discovery merges sources in a fixed order and de-duplicates first-wins."""
|
|
164
297
|
|
|
@@ -5,10 +5,18 @@ from importlib import metadata
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from openedx_authz.engine.schema.discovery import
|
|
8
|
+
from openedx_authz.engine.schema.discovery import (
|
|
9
|
+
SETTINGS_FILE_SENTINEL,
|
|
10
|
+
SETTINGS_FILE_VERSION,
|
|
11
|
+
DiscoveredFileResource,
|
|
12
|
+
DiscoveredResource,
|
|
13
|
+
Origin,
|
|
14
|
+
SchemaDiscovery,
|
|
15
|
+
)
|
|
9
16
|
from openedx_authz.engine.schema.exceptions import SchemaLoadError
|
|
10
17
|
from openedx_authz.engine.schema.loading import SchemaLoader
|
|
11
18
|
from openedx_authz.engine.schema.parsing import UNKNOWN_DISTRIBUTION, SchemaParser
|
|
19
|
+
from openedx_authz.engine.schema.types import SourceFileRecord
|
|
12
20
|
|
|
13
21
|
from .factories import InMemoryResource
|
|
14
22
|
|
|
@@ -106,10 +114,15 @@ class TestDocumentLoading:
|
|
|
106
114
|
assert docs[0].categories == []
|
|
107
115
|
assert docs[0].roles == []
|
|
108
116
|
|
|
109
|
-
def
|
|
110
|
-
"""An empty
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
def test_completely_empty_file_yields_no_documents(self):
|
|
118
|
+
"""An empty file parses to no YAML documents and yields an empty list.
|
|
119
|
+
|
|
120
|
+
With ``safe_load_all``, an empty stream produces no documents (ADR 0030:
|
|
121
|
+
empty documents are skipped silently). Validation is not reached.
|
|
122
|
+
"""
|
|
123
|
+
docs = _load(b"")
|
|
124
|
+
|
|
125
|
+
assert not docs
|
|
113
126
|
|
|
114
127
|
def test_invalid_yaml_raises_load_error(self):
|
|
115
128
|
"""Malformed YAML surfaces as ``SchemaLoadError``."""
|
|
@@ -619,3 +632,152 @@ class TestDistributionAmbiguityLogging:
|
|
|
619
632
|
assert result1 == result2 == "dist1"
|
|
620
633
|
# Only one log record because the warn_key is identical
|
|
621
634
|
assert len(caplog.records) == 1
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
class TestMultiDocumentStreams:
|
|
638
|
+
"""Multi-document YAML loading (ADR 0030).
|
|
639
|
+
|
|
640
|
+
A single schema file may contain multiple ``---``-separated YAML documents,
|
|
641
|
+
each a complete, independent contribution with its own ``schema_version``
|
|
642
|
+
and ``priority``. This is how Tutor concatenates several plugin
|
|
643
|
+
contributions into one rendered file.
|
|
644
|
+
"""
|
|
645
|
+
|
|
646
|
+
MULTI_DOC = b"""
|
|
647
|
+
schema_version: '1.0'
|
|
648
|
+
priority: 200
|
|
649
|
+
|
|
650
|
+
role_extensions:
|
|
651
|
+
- role_id: course_editor
|
|
652
|
+
add_permissions: [courses.export_course]
|
|
653
|
+
---
|
|
654
|
+
schema_version: '1.0'
|
|
655
|
+
priority: 100
|
|
656
|
+
|
|
657
|
+
role_extensions:
|
|
658
|
+
- role_id: course_editor
|
|
659
|
+
remove_permissions: [courses.manage_tags]
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
def test_multi_document_stream_yields_multiple_documents(self):
|
|
663
|
+
"""Two ``---``-separated documents yield two :class:`SchemaDocument` objects."""
|
|
664
|
+
docs = _load(self.MULTI_DOC)
|
|
665
|
+
|
|
666
|
+
assert len(docs) == 2
|
|
667
|
+
assert docs[0].priority == 200
|
|
668
|
+
assert docs[1].priority == 100
|
|
669
|
+
|
|
670
|
+
def test_each_document_has_its_own_schema_version(self):
|
|
671
|
+
"""Each document carries its own ``schema_version``."""
|
|
672
|
+
docs = _load(self.MULTI_DOC)
|
|
673
|
+
|
|
674
|
+
assert docs[0].source.schema_version == "1.0"
|
|
675
|
+
assert docs[1].source.schema_version == "1.0"
|
|
676
|
+
|
|
677
|
+
def test_each_document_has_independent_extensions(self):
|
|
678
|
+
"""Extensions from separate documents stay separate."""
|
|
679
|
+
docs = _load(self.MULTI_DOC)
|
|
680
|
+
|
|
681
|
+
assert docs[0].role_extensions[0].add_permissions == ("courses.export_course",)
|
|
682
|
+
assert docs[1].role_extensions[0].remove_permissions == ("courses.manage_tags",)
|
|
683
|
+
|
|
684
|
+
def test_trailing_separator_is_ignored(self):
|
|
685
|
+
"""A trailing ``---`` produces a ``None`` document that is skipped."""
|
|
686
|
+
contents = b"schema_version: '1.0'\npriority: 1\n---\n"
|
|
687
|
+
|
|
688
|
+
docs = _load(contents)
|
|
689
|
+
|
|
690
|
+
assert len(docs) == 1
|
|
691
|
+
assert docs[0].priority == 1
|
|
692
|
+
|
|
693
|
+
def test_empty_document_between_separators_is_skipped(self):
|
|
694
|
+
"""An empty ``---`` block between contributions is silently dropped."""
|
|
695
|
+
contents = (
|
|
696
|
+
b"schema_version: '1.0'\npriority: 1\n"
|
|
697
|
+
b"---\n"
|
|
698
|
+
b"---\n"
|
|
699
|
+
b"schema_version: '1.0'\npriority: 2\n"
|
|
700
|
+
)
|
|
701
|
+
|
|
702
|
+
docs = _load(contents)
|
|
703
|
+
|
|
704
|
+
assert len(docs) == 2
|
|
705
|
+
assert docs[0].priority == 1
|
|
706
|
+
assert docs[1].priority == 2
|
|
707
|
+
|
|
708
|
+
def test_comment_only_document_is_skipped(self):
|
|
709
|
+
"""A document consisting solely of comments parses as ``None`` and is skipped."""
|
|
710
|
+
contents = b"# Just a comment\n---\nschema_version: '1.0'\npriority: 1\n"
|
|
711
|
+
|
|
712
|
+
docs = _load(contents)
|
|
713
|
+
|
|
714
|
+
assert len(docs) == 1
|
|
715
|
+
assert docs[0].priority == 1
|
|
716
|
+
|
|
717
|
+
def test_non_mapping_document_in_stream_raises(self):
|
|
718
|
+
"""A non-mapping document embedded in a stream raises ``SchemaLoadError``."""
|
|
719
|
+
contents = b"schema_version: '1.0'\npriority: 1\n---\n- just\n- a\n- list\n"
|
|
720
|
+
|
|
721
|
+
with pytest.raises(SchemaLoadError, match="schema document must be a mapping"):
|
|
722
|
+
_load(contents)
|
|
723
|
+
|
|
724
|
+
def test_all_documents_share_the_same_content_digest(self):
|
|
725
|
+
"""The content digest is per-resource (file), not per-document.
|
|
726
|
+
|
|
727
|
+
Multiple documents from the same file share the same digest since it is
|
|
728
|
+
computed from the raw file bytes.
|
|
729
|
+
"""
|
|
730
|
+
docs = _load(self.MULTI_DOC)
|
|
731
|
+
|
|
732
|
+
assert docs[0].source.content_digest == docs[1].source.content_digest
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class TestFileSourceIdentity:
|
|
736
|
+
"""Source identity for filesystem-path resources (ADR 0030).
|
|
737
|
+
|
|
738
|
+
Filesystem resources use sentinel values for distribution and version,
|
|
739
|
+
and ``SourceFileRecord`` overrides ``source_id`` to avoid garbling the
|
|
740
|
+
absolute path.
|
|
741
|
+
"""
|
|
742
|
+
|
|
743
|
+
@staticmethod
|
|
744
|
+
def _load_file(tmp_path, contents: bytes = b"schema_version: '1.0'\npriority: 1\n"):
|
|
745
|
+
"""Write a temp schema file, discover it, and load it."""
|
|
746
|
+
schema_file = tmp_path / "authz-schema.yaml"
|
|
747
|
+
schema_file.write_bytes(contents)
|
|
748
|
+
resource = DiscoveredFileResource.from_path(str(schema_file))
|
|
749
|
+
parsed_documents = SchemaParser().parse([resource])
|
|
750
|
+
return SchemaLoader().load(parsed_documents)
|
|
751
|
+
|
|
752
|
+
def test_distribution_is_the_sentinel(self, tmp_path):
|
|
753
|
+
"""Filesystem resources use the ``<tutor-patch>`` sentinel as distribution."""
|
|
754
|
+
docs = self._load_file(tmp_path)
|
|
755
|
+
|
|
756
|
+
assert docs[0].source.distribution == SETTINGS_FILE_SENTINEL
|
|
757
|
+
|
|
758
|
+
def test_distribution_version_is_the_sentinel(self, tmp_path):
|
|
759
|
+
"""Filesystem resources use ``0.0.0`` as their version sentinel."""
|
|
760
|
+
docs = self._load_file(tmp_path)
|
|
761
|
+
|
|
762
|
+
assert docs[0].source.distribution_version == SETTINGS_FILE_VERSION
|
|
763
|
+
|
|
764
|
+
def test_source_id_uses_the_absolute_path(self, tmp_path):
|
|
765
|
+
"""``source_id`` is ``"<tutor-patch>:<absolute_path>"`` — no dot corruption."""
|
|
766
|
+
docs = self._load_file(tmp_path)
|
|
767
|
+
expected_path = str(tmp_path / "authz-schema.yaml")
|
|
768
|
+
|
|
769
|
+
assert docs[0].source.source_id == f"{SETTINGS_FILE_SENTINEL}:{expected_path}"
|
|
770
|
+
|
|
771
|
+
def test_source_record_is_a_source_file_record(self, tmp_path):
|
|
772
|
+
"""The loader produces a ``SourceFileRecord`` for filesystem resources."""
|
|
773
|
+
docs = self._load_file(tmp_path)
|
|
774
|
+
|
|
775
|
+
assert isinstance(docs[0].source, SourceFileRecord)
|
|
776
|
+
|
|
777
|
+
def test_module_and_resource_path_are_the_absolute_path(self, tmp_path):
|
|
778
|
+
"""Both ``module`` and ``resource_path`` record the absolute filesystem path."""
|
|
779
|
+
docs = self._load_file(tmp_path)
|
|
780
|
+
expected_path = str(tmp_path / "authz-schema.yaml")
|
|
781
|
+
|
|
782
|
+
assert docs[0].source.module == expected_path
|
|
783
|
+
assert docs[0].source.resource_path == expected_path
|
|
@@ -15,7 +15,6 @@ from openedx_authz.tests.schema.factories import InMemoryResource
|
|
|
15
15
|
("contents", "expected"),
|
|
16
16
|
[
|
|
17
17
|
(b"schema_version: '1.0'\npriority: 10\n", {"schema_version": "1.0", "priority": 10}),
|
|
18
|
-
(b"", None),
|
|
19
18
|
(b"value", "value"),
|
|
20
19
|
(b"- first\n- second\n", ["first", "second"]),
|
|
21
20
|
],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openedx-authz
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|