From 4550ac1a0297009dc2b2af0ceaa073a5e737e070 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Thu, 27 Aug 2026 17:19:21 +0200 Subject: [PATCH 1/8] docs: define authorization schema discovery --- .../0019-authorization-schema-discovery.rst | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 docs/decisions/0019-authorization-schema-discovery.rst diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst new file mode 100644 index 00000000..0b3803ff --- /dev/null +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -0,0 +1,69 @@ +0019: Discover and Load Authorization Schemas During Deployment +############################################################### + +Status +****** + +**Draft** + +Context +******* + +Applications, such as Django apps or IDAs, need a standard way to provide their static authz schema files. Because Open edX supports several deployment methods, discovery must work with Tutor, native deployments and local development. + +Decision +******** + +#. Python entry point and package resources +=========================================== + +An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with ``importlib.resources`` and returns all contributions in a defined order, since Python package discovery order may vary. + +For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_roles.authz.yaml`` and ``authz/course_permissions.authz.yaml``. + +#. Static source information +============================ + +For every contribution, the compiler records: + +* the installed distribution name and version; +* the Python module that owns the resource; +* the resource path inside that module; and +* the schema version and content digest. + +Together, these values identify the same source across deployment layouts. The loader reads them from the package and uses them as the source record. + +The compiler records this information for each definition and role-permission assignment. For example, ``openedx-authz:openedx_authz/definitions/core.authz.yaml`` may assign ``courses.view_course`` to ``course_admin``, while ``course-authoring:course_authoring/authz/course_authoring.authz.yaml`` assigns ``courses.edit_schedule`` to the same role. Because both resources contributed to the compiled role, it keeps both source records. + +#. Deployment command +===================== + +``openedx-authz`` exposes one non-interactive command that discovers, validates, compiles, reports, and applies the static schema. For CI and local development, the same command can accept explicit resources or directories. + +Tutor calls the command via for example a plugin initialization task, while other deployment systems call it before their application processes begin serving traffic. Each integration chooses the appropriate hook, but all of them use the same compiler. + +#. Removed applications +======================= + +When an application is disabled or removed, the next deployment removes the static definitions that came only from that application. If users are assigned to one of its roles, deployment stops and reports those assignments so that an operator can remove them or move the users to another role. Shared definitions remain available when another application still provides them. + +Consequences +************ + +* An application can ship authorization definitions with its code. +* Tutor and other deployment systems use the same mechanism to discover and load the definitions. +* Source information remains consistent across container. +* Authorization changes after the deployment command runs successfully. +* Packaging checks must verify that schema resources are included in wheels and source distributions. +* Deployment integrations need to pass database settings and run the command at a point where all contributing packages are installed. + +References +********** + +* `ADR 0018`_ +* `Tutor plugin development`_ +* `Tutor plugin template`_ + +.. _ADR 0018: 0018-authorization-schema-lifecycle.rst +.. _Tutor plugin development: https://docs.tutor.edly.io/plugins/v0/gettingstarted.html +.. _Tutor plugin template: https://github.com/overhangio/cookiecutter-tutor-plugin/blob/master/%7B%7B%20cookiecutter.package_name%20%7D%7D/%7B%7B%20cookiecutter.module_name%20%7D%7D/plugin.py From 961062422d62076bedae8200c3c219b526196658 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Tue, 1 Sep 2026 18:35:38 +0200 Subject: [PATCH 2/8] docs: render decision section numbers --- docs/decisions/0019-authorization-schema-discovery.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index 0b3803ff..25a29651 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -14,14 +14,14 @@ Applications, such as Django apps or IDAs, need a standard way to provide their Decision ******** -#. Python entry point and package resources +1. Python entry point and package resources =========================================== An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with ``importlib.resources`` and returns all contributions in a defined order, since Python package discovery order may vary. For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_roles.authz.yaml`` and ``authz/course_permissions.authz.yaml``. -#. Static source information +2. Static source information ============================ For every contribution, the compiler records: @@ -35,14 +35,14 @@ Together, these values identify the same source across deployment layouts. The l The compiler records this information for each definition and role-permission assignment. For example, ``openedx-authz:openedx_authz/definitions/core.authz.yaml`` may assign ``courses.view_course`` to ``course_admin``, while ``course-authoring:course_authoring/authz/course_authoring.authz.yaml`` assigns ``courses.edit_schedule`` to the same role. Because both resources contributed to the compiled role, it keeps both source records. -#. Deployment command +3. Deployment command ===================== ``openedx-authz`` exposes one non-interactive command that discovers, validates, compiles, reports, and applies the static schema. For CI and local development, the same command can accept explicit resources or directories. Tutor calls the command via for example a plugin initialization task, while other deployment systems call it before their application processes begin serving traffic. Each integration chooses the appropriate hook, but all of them use the same compiler. -#. Removed applications +4. Removed applications ======================= When an application is disabled or removed, the next deployment removes the static definitions that came only from that application. If users are assigned to one of its roles, deployment stops and reports those assignments so that an operator can remove them or move the users to another role. Shared definitions remain available when another application still provides them. From c47377dca57e7152b028d123f8c6ac834a1a0ea3 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 2 Sep 2026 18:46:05 +0200 Subject: [PATCH 3/8] docs: clarify authorization schema discovery --- docs/decisions/0019-authorization-schema-discovery.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index 25a29651..d06ed658 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -17,9 +17,9 @@ Decision 1. Python entry point and package resources =========================================== -An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with ``importlib.resources`` and returns all contributions in a defined order, since Python package discovery order may vary. +An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with the available mechanisms (like we discover Django applications or using ``importlib.resources``) and returns all contributions in a defined order, since package discovery order may vary. -For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_roles.authz.yaml`` and ``authz/course_permissions.authz.yaml``. +For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_authoring.authz.yaml``. 2. Static source information ============================ From 61e46f6ecf89adf36b946ed2aed7ddbc8f29d142 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 2 Sep 2026 19:03:03 +0200 Subject: [PATCH 4/8] docs: clarify schema entry point discovery --- docs/decisions/0019-authorization-schema-discovery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index d06ed658..bdf0a7c8 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -19,7 +19,7 @@ Decision An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with the available mechanisms (like we discover Django applications or using ``importlib.resources``) and returns all contributions in a defined order, since package discovery order may vary. -For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_authoring.authz.yaml``. +For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_authoring.authz.yaml``. The entrypoint, for example called authz.schema, could be called by the compiler to discover all available schema resources. 2. Static source information ============================ From 2d5e92ce6430b779d1c3e423f45347c261341f01 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 2 Sep 2026 19:03:57 +0200 Subject: [PATCH 5/8] docs: explain schema entry point discovery --- docs/decisions/0019-authorization-schema-discovery.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index bdf0a7c8..a6ee0847 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -19,7 +19,7 @@ Decision An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with the available mechanisms (like we discover Django applications or using ``importlib.resources``) and returns all contributions in a defined order, since package discovery order may vary. -For example, a ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` and return ``authz/course_authoring.authz.yaml``. The entrypoint, for example called authz.schema, could be called by the compiler to discover all available schema resources. +For example, the ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` under an entry-point group such as ``authz.schema``. The compiler loads that group to discover ``authz/course_authoring.authz.yaml`` and the schema resources provided by other applications. 2. Static source information ============================ From e6d8c9d9057da428bd9dd280891e9e12e655d49e Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 2 Sep 2026 19:06:50 +0200 Subject: [PATCH 6/8] docs: add schema entry point example --- .../0019-authorization-schema-discovery.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index a6ee0847..9e26cfc8 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -19,7 +19,21 @@ Decision An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with the available mechanisms (like we discover Django applications or using ``importlib.resources``) and returns all contributions in a defined order, since package discovery order may vary. -For example, the ``course_authoring`` package can register ``course_authoring.authz:get_schema_resources`` under an entry-point group such as ``authz.schema``. The compiler loads that group to discover ``authz/course_authoring.authz.yaml`` and the schema resources provided by other applications. +For example, the ``course_authoring`` package can register its schema function in ``pyproject.toml``: + +.. code-block:: toml + + [project.entry-points."authz.schema"] + course_authoring = "course_authoring.authz:get_schema_resources" + +The function returns the schema resources provided by the package: + +.. code-block:: python + + def get_schema_resources(): + return ["authz/course_authoring.authz.yaml"] + +The compiler loads the ``authz.schema`` entry-point group and calls each registered function to discover the available schema resources. 2. Static source information ============================ From 0802a83500e09f52683dc98360cb43aeadfceffd Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Wed, 2 Sep 2026 19:14:19 +0200 Subject: [PATCH 7/8] docs: document application and Tutor schema discovery --- .../0019-authorization-schema-discovery.rst | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index 9e26cfc8..89aff070 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -17,23 +17,44 @@ Decision 1. Python entry point and package resources =========================================== -An application contributes one or more authz schema resources through a Python entry point defined by ``openedx-authz``. Discovery resolves those resources with the available mechanisms (like we discover Django applications or using ``importlib.resources``) and returns all contributions in a defined order, since package discovery order may vary. +Applications can register schema resources through a Python entry point defined by ``openedx-authz``, following the pattern used to register LMS and CMS Django apps. For example, the ``course_authoring`` package can add an ``authz.schema`` entry point in ``setup.py``: -For example, the ``course_authoring`` package can register its schema function in ``pyproject.toml``: - -.. code-block:: toml +.. code-block:: python - [project.entry-points."authz.schema"] - course_authoring = "course_authoring.authz:get_schema_resources" + entry_points={ + "authz.schema": [ + "course_authoring = course_authoring.authz:get_schema_resources", + ], + } -The function returns the schema resources provided by the package: +The registered function returns the package resources that contain its schema: .. code-block:: python + ... def get_schema_resources(): return ["authz/course_authoring.authz.yaml"] -The compiler loads the ``authz.schema`` entry-point group and calls each registered function to discover the available schema resources. +The compiler loads the ``authz.schema`` entry-point group, calls each registered function, and uses ``importlib.resources`` or a similar mechanism to find the files. It then validates the schemas, compiles them into Casbin rows, and applies them to the database. + +Site operators can contribute a schema through the ``openedx-authz-schema`` Tutor patch: + +.. code-block:: yaml + + name: openedx-authz-overrides + version: 0.1.0 + + patches: + openedx-authz-schema: | + schema_version: "1.0" + priority: 200 + + role_extensions: + - role: course_editor + add_permissions: + - courses.export_course + +The compiler combines schemas from application entry points and Tutor patches in a defined order because discovery order may vary. 2. Static source information ============================ From b3626aa5f1ec54c11011f75ff03962b2fc3f8f99 Mon Sep 17 00:00:00 2001 From: Maria Grimaldi Date: Fri, 4 Sep 2026 14:31:34 +0200 Subject: [PATCH 8/8] docs: clarify schema discovery failures --- docs/decisions/0019-authorization-schema-discovery.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0019-authorization-schema-discovery.rst b/docs/decisions/0019-authorization-schema-discovery.rst index 89aff070..6728150f 100644 --- a/docs/decisions/0019-authorization-schema-discovery.rst +++ b/docs/decisions/0019-authorization-schema-discovery.rst @@ -37,6 +37,8 @@ The registered function returns the package resources that contain its schema: The compiler loads the ``authz.schema`` entry-point group, calls each registered function, and uses ``importlib.resources`` or a similar mechanism to find the files. It then validates the schemas, compiles them into Casbin rows, and applies them to the database. +If a registered function raises an exception, discovery stops and reports which application failed. This prevents deployment from continuing with an incomplete set of static definitions. + Site operators can contribute a schema through the ``openedx-authz-schema`` Tutor patch: .. code-block:: yaml @@ -82,12 +84,14 @@ Tutor calls the command via for example a plugin initialization task, while othe When an application is disabled or removed, the next deployment removes the static definitions that came only from that application. If users are assigned to one of its roles, deployment stops and reports those assignments so that an operator can remove them or move the users to another role. Shared definitions remain available when another application still provides them. +Deployment also stops if a remaining schema refers to a role, permission, or category that would be removed with the application. An operator may allow the removal through explicit deployment configuration. Without that configuration, the stored definitions remain unchanged. + Consequences ************ * An application can ship authorization definitions with its code. * Tutor and other deployment systems use the same mechanism to discover and load the definitions. -* Source information remains consistent across container. +* Source information remains consistent across containers. * Authorization changes after the deployment command runs successfully. * Packaging checks must verify that schema resources are included in wheels and source distributions. * Deployment integrations need to pass database settings and run the command at a point where all contributing packages are installed.