From bcab70f7ef68ef85db05b6c01059930d1d6b4aee Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Wed, 18 Mar 2026 15:56:42 -0500 Subject: [PATCH 1/6] docs: add adr-0012-glob-support-for-role-assignments --- ...0012-glob-support-for-role-assignments.rst | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 docs/decisions/0012-glob-support-for-role-assignments.rst diff --git a/docs/decisions/0012-glob-support-for-role-assignments.rst b/docs/decisions/0012-glob-support-for-role-assignments.rst new file mode 100644 index 00000000..38e6bc29 --- /dev/null +++ b/docs/decisions/0012-glob-support-for-role-assignments.rst @@ -0,0 +1,154 @@ +0012: Glob Support For Role Assignments +####################################### + +Status +****** + +**Draft** - *2026-03-18* + +Context +******* + +The current authorization system is based on Casbin and models: + +- **Permissions per role** (``p`` policies), where the ``scope`` field may already use patterns (for example, ``lib^*``) through matcher functions in the model. +- **Role assignments** (``g`` policies), which link a subject to a role within a scope. + +The current Casbin model treats the ``scope`` field in ``g`` policies as an **exact match**. This is sufficient when roles are granted for a single, concrete scope value, but it is limiting when operators need to: + +- Assign roles that are valid for a **set of resources** that share a common prefix (for example, all courses or all content libraries belonging to a given organization). +- Avoid enumerating a large or evolving set of resources one by one, which increases operational overhead and risk of drift. + +This ADR proposes enabling glob-like matching for role assignments, so that a single ``g`` policy can represent a multi-scope assignment such as: + +.. code:: text + + g, user^contributor, role^course_staff, course-v1^course-v1:OpenedX+* + +In that example, checking a permission such as: + +.. code:: text + + authz_api.is_user_allowed("contributor", "courses.manage_advanced_settings", "course-v1:OpenedX+Some+Course") + +should be allowed if the user's role assignment matches the ``course-v1:OpenedX+*`` pattern. + +At the same time, we must preserve the guarantees of the authorization model: + +- **Safety**: Glob patterns must not accidentally grant permissions outside of the intended boundary. +- **Clarity**: Patterns must be easy to understand and reason about for operators and auditors. +- **Extensibility**: The mechanism should be general enough to support future use cases without requiring a redesign of the model. + +Decision +******** + +We will introduce support for glob-like matching on the ``scope`` field of **role assignments** (``g`` policies), combined with explicit validation in the public APIs that manage those assignments. + +The decision is intentionally **general**: the core change is to allow glob matching for scopes in ``g`` policies, and to guard its usage with well-defined, namespace-specific validation rules. This creates a foundation that can be extended later without changing the Casbin model again. + +1. Enable glob matching on ``g`` scopes in the enforcer +======================================================= + +We will configure the ``AuthzEnforcer`` to use a domain/scope matching function for ``g`` policies that supports glob-like suffixes. Concretely: + +- The enforcer will register a domain matching function for the ``g`` (grouping) function (for example, using ``key_match_func``). +- This matching function will treat ``*`` as a wildcard at the **end** of the string. That is, patterns such as ``course-v1:OpenedX+*`` will match ``course-v1:OpenedX+SOME+COURSE``, but the model will not rely on complex patterns or regular expressions. +- Existing ``g`` policies that use exact scopes remain valid and continue to behave identically. + +This change allows the Casbin engine to evaluate role assignments that apply to a family of scopes instead of a single exact value, without modifying the underlying storage schema (``CasbinRule``) or the overall request format (``r = sub, act, scope``). + +2. Validate glob scopes at the API boundary +=========================================== + +All APIs that create, update, or delete role assignments (i.e., policies of type ``g``) must validate any scope that includes a glob pattern. The goals of validation are: + +- **Constrain** what forms of glob are permitted for each namespace. +- **Reject** malformed or overly broad patterns that would be difficult to reason about or audit. + +The following rules apply initially: + +- The glob character (``*``) is only supported as a **suffix wildcard**. It cannot appear in the middle of a scope identifier. +- A glob pattern represents a **bounded prefix match** for the external key portion of a scope within its namespace. The API validation ensures the prefix is meaningful (i.e., it corresponds to a valid identifier boundary for that namespace), so the glob cannot be used to accidentally broaden access. +- For any glob patterns (courses, libraries, or future namespaces), malformed inputs (such as mid-string wildcards or prefixes that do not match the expected key format/boundaries) are **rejected**. +- Additional namespaces must define their own, explicit validation rules before accepting glob scopes. +- As needs evolve, more glob types can be added safely by introducing namespace-specific semantics and validations (for example, additional prefix boundaries such as program/tenant prefixes, or narrower matching strategies if required). + +These validation rules are implemented in the Open edX layer (API / data layer), not in the Casbin matcher itself. The enforcer remains general-purpose. The domain-specific semantics of what constitutes an acceptable glob pattern are enforced at the boundary where user/operator input is turned into policies. + +3. Keep the model general and extensible +======================================== + +By introducing glob support in role assignments in a constrained way, we unlock a set of future extensions without redesigning the model: + +- **Other scope types** + + - Scope types with hierarchical or prefix-based identifiers (for example, libraries or other content groupings) can adopt glob support by: + + - Defining their own namespace-specific rules for valid suffix globs. + - Reusing the same enforcer-level domain matching capability. + +- **Future matching strategies** + + - If, in the future, there is a strong need for more expressive matching (for example, segment-based matching or multiple wildcards), these can be introduced as **new, explicitly-scoped features** with their own validation rules and migration story. + - For now, we deliberately keep glob support simple and limited (single trailing ``*``) to minimize complexity and security risk. + +Consequences +************ + +Positive consequences +===================== + +- **Increased expressiveness**: Operators can express multi-scope role assignments (for example, "course staff for all courses in organization OpenedX") without enumerating each course in individual ``g`` policies. +- **Reduced operational overhead**: New resources that fall under an existing glob pattern automatically inherit the appropriate role assignments, reducing the need for ongoing manual updates. +- **Better alignment with real-world use cases**: Many organizational setups naturally require "all resources under this prefix" semantics. Glob support maps directly to those needs. +- **Clear extension path**: The mechanism is generic enough to be reused for other namespaces (such as organization or library scopes), as long as each namespace defines and enforces its own validation rules. + +Negative consequences / risks +============================= + +- **Security and safety**: If validation is misconfigured or bypassed, glob patterns could unintentionally grant access beyond the intended boundary. This risk is mitigated by: + + - Enforcing validation in the Open edX API layer. + - Restricting globs to trailing ``*`` patterns. + - Defining precise, namespace-specific rules. + +- **Complexity in mental model**: Operators and developers must understand that some role assignments apply to families of scopes instead of a single scope. This can be addressed by: + + - Providing clear documentation and examples for glob-based assignments. + - Exposing introspection tooling that explains which policies matched a given decision. + +- **Performance considerations**: Glob matching adds some overhead to Casbin evaluations. However: + + - The cost of simple suffix matching is low. + - The policy store still uses the same schema and indexing strategy. + - The feature should be used primarily for coarse-grained groupings (e.g., by organization), not for highly fragmented patterns. + +Rejected Alternatives +********************** + +- **Keep exact matching only for 'g' scopes** + + - Pros: + + - Simpler to reason about. + - No changes to matcher configuration. + - Cons: + + - Does not scale for environments with many resources per organization. + - Forces operators to maintain large numbers of nearly-identical assignments. + +- **Introduce full regular-expression support on scopes** + + - Pros: + + - Maximum flexibility for expressing patterns. + - Cons: + + - Harder to reason about and audit. + - Higher risk of misconfiguration and security overshoot. + - Potentially worse performance. + +References +********** + +- `Casbin function documentation (matching functions) `_ From bebe16f39810540d59a990e45986bdd37ff7ff51 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 19 Mar 2026 09:33:47 -0500 Subject: [PATCH 2/6] chore: update changelog --- CHANGELOG.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ae498346..c03c0251 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ Change Log Unreleased ********** +* Add ADR for global scope support for role assignments. + 1.2.0 - 2026-03-30 ****************** @@ -35,9 +37,9 @@ Added ****************** Removed -======= * Dropped support for Python 3.11. +======= 0.23.0 - 2026-02-18 ******************** From 9a1707ed9f52d72a8773007b75daea69b21db864 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Tue, 24 Mar 2026 12:24:25 -0500 Subject: [PATCH 3/6] chore: address pr review --- .../0012-glob-support-for-role-assignments.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0012-glob-support-for-role-assignments.rst b/docs/decisions/0012-glob-support-for-role-assignments.rst index 38e6bc29..fc558029 100644 --- a/docs/decisions/0012-glob-support-for-role-assignments.rst +++ b/docs/decisions/0012-glob-support-for-role-assignments.rst @@ -53,6 +53,7 @@ We will configure the ``AuthzEnforcer`` to use a domain/scope matching function - The enforcer will register a domain matching function for the ``g`` (grouping) function (for example, using ``key_match_func``). - This matching function will treat ``*`` as a wildcard at the **end** of the string. That is, patterns such as ``course-v1:OpenedX+*`` will match ``course-v1:OpenedX+SOME+COURSE``, but the model will not rely on complex patterns or regular expressions. +- Matching is **case-sensitive**. Scope comparisons follow exact string semantics for non-wildcard characters (for example, ``course-v1:openedx+*`` does not match ``course-v1:OpenedX+...``). - Existing ``g`` policies that use exact scopes remain valid and continue to behave identically. This change allows the Casbin engine to evaluate role assignments that apply to a family of scopes instead of a single exact value, without modifying the underlying storage schema (``CasbinRule``) or the overall request format (``r = sub, act, scope``). @@ -70,9 +71,22 @@ The following rules apply initially: - The glob character (``*``) is only supported as a **suffix wildcard**. It cannot appear in the middle of a scope identifier. - A glob pattern represents a **bounded prefix match** for the external key portion of a scope within its namespace. The API validation ensures the prefix is meaningful (i.e., it corresponds to a valid identifier boundary for that namespace), so the glob cannot be used to accidentally broaden access. - For any glob patterns (courses, libraries, or future namespaces), malformed inputs (such as mid-string wildcards or prefixes that do not match the expected key format/boundaries) are **rejected**. -- Additional namespaces must define their own, explicit validation rules before accepting glob scopes. +- Additional namespaces must define their own, explicit validation rules before accepting glob scopes. If a namespace does not have a custom matcher/validator, any ``*`` in its scope is rejected and only exact scopes are accepted. - As needs evolve, more glob types can be added safely by introducing namespace-specific semantics and validations (for example, additional prefix boundaries such as program/tenant prefixes, or narrower matching strategies if required). +Examples of valid/invalid namespace scope globs +----------------------------------------------- + +Given the current suffix-glob policy (single trailing ``*`` at a namespace-approved boundary): + +- Invalid: ``*`` (unbounded across all namespaces/scopes). +- Invalid: ``c*`` or ``l*`` (not a valid namespaced scope prefix). +- Invalid: ``course-v1*`` or ``lib*`` (missing ``:`` and required namespace structure). +- Invalid: ``course-v1:*`` or ``lib:*`` (wildcard starts before a bounded prefix inside the ``course-v1`` or ``lib`` key). +- Invalid: ``course-v1:O*`` or ``lib:MIT*`` (wildcard starts mid-segment. Prefix boundary is not complete). +- Valid: ``course-v1:OpenedX+*`` (wildcard starts at an approved boundary for the ``course-v1`` namespace). +- Valid: ``lib:MITx:*`` (wildcard starts at an approved boundary for the ``lib`` namespace). + These validation rules are implemented in the Open edX layer (API / data layer), not in the Casbin matcher itself. The enforcer remains general-purpose. The domain-specific semantics of what constitutes an acceptable glob pattern are enforced at the boundary where user/operator input is turned into policies. 3. Keep the model general and extensible From bb7812023e07eefcb6d5288a4e33d9867a3b13a6 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 26 Mar 2026 10:56:33 -0500 Subject: [PATCH 4/6] chore: address pr review --- docs/decisions/0012-glob-support-for-role-assignments.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0012-glob-support-for-role-assignments.rst b/docs/decisions/0012-glob-support-for-role-assignments.rst index fc558029..ff7cdb67 100644 --- a/docs/decisions/0012-glob-support-for-role-assignments.rst +++ b/docs/decisions/0012-glob-support-for-role-assignments.rst @@ -11,7 +11,7 @@ Context The current authorization system is based on Casbin and models: -- **Permissions per role** (``p`` policies), where the ``scope`` field may already use patterns (for example, ``lib^*``) through matcher functions in the model. +- **Permissions per role** (``p`` policies), where the ``scope`` field may already include the existing namespace wildcard ``^*`` (for example, ``lib^*`` meaning "any scope in the ``lib`` namespace"). - **Role assignments** (``g`` policies), which link a subject to a role within a scope. The current Casbin model treats the ``scope`` field in ``g`` policies as an **exact match**. This is sufficient when roles are granted for a single, concrete scope value, but it is limiting when operators need to: @@ -54,7 +54,7 @@ We will configure the ``AuthzEnforcer`` to use a domain/scope matching function - The enforcer will register a domain matching function for the ``g`` (grouping) function (for example, using ``key_match_func``). - This matching function will treat ``*`` as a wildcard at the **end** of the string. That is, patterns such as ``course-v1:OpenedX+*`` will match ``course-v1:OpenedX+SOME+COURSE``, but the model will not rely on complex patterns or regular expressions. - Matching is **case-sensitive**. Scope comparisons follow exact string semantics for non-wildcard characters (for example, ``course-v1:openedx+*`` does not match ``course-v1:OpenedX+...``). -- Existing ``g`` policies that use exact scopes remain valid and continue to behave identically. +- Existing ``g`` policies that use exact scopes remain valid and continue to behave identically. They follow the same validation path as before. Only scopes containing a ``*`` suffix glob take a different API-side validation path. This change allows the Casbin engine to evaluate role assignments that apply to a family of scopes instead of a single exact value, without modifying the underlying storage schema (``CasbinRule``) or the overall request format (``r = sub, act, scope``). From aa85823c1f817d4c4db885bb8c9376dee5ecdb63 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 26 Mar 2026 11:32:29 -0500 Subject: [PATCH 5/6] docs: refine terms in adr --- ...0012-glob-support-for-role-assignments.rst | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/decisions/0012-glob-support-for-role-assignments.rst b/docs/decisions/0012-glob-support-for-role-assignments.rst index ff7cdb67..07f2aaf5 100644 --- a/docs/decisions/0012-glob-support-for-role-assignments.rst +++ b/docs/decisions/0012-glob-support-for-role-assignments.rst @@ -35,7 +35,7 @@ should be allowed if the user's role assignment matches the ``course-v1:OpenedX+ At the same time, we must preserve the guarantees of the authorization model: -- **Safety**: Glob patterns must not accidentally grant permissions outside of the intended boundary. +- **Safety**: Glob patterns must not accidentally grant permissions outside the intended identifier boundary. - **Clarity**: Patterns must be easy to understand and reason about for operators and auditors. - **Extensibility**: The mechanism should be general enough to support future use cases without requiring a redesign of the model. @@ -52,7 +52,7 @@ The decision is intentionally **general**: the core change is to allow glob matc We will configure the ``AuthzEnforcer`` to use a domain/scope matching function for ``g`` policies that supports glob-like suffixes. Concretely: - The enforcer will register a domain matching function for the ``g`` (grouping) function (for example, using ``key_match_func``). -- This matching function will treat ``*`` as a wildcard at the **end** of the string. That is, patterns such as ``course-v1:OpenedX+*`` will match ``course-v1:OpenedX+SOME+COURSE``, but the model will not rely on complex patterns or regular expressions. +- This matching function will treat ``*`` as a wildcard at the **end** of the string (suffix wildcard). That is, patterns such as ``course-v1:OpenedX+*`` will match ``course-v1:OpenedX+SOME+COURSE``, but the model will not rely on complex patterns or regular expressions. - Matching is **case-sensitive**. Scope comparisons follow exact string semantics for non-wildcard characters (for example, ``course-v1:openedx+*`` does not match ``course-v1:OpenedX+...``). - Existing ``g`` policies that use exact scopes remain valid and continue to behave identically. They follow the same validation path as before. Only scopes containing a ``*`` suffix glob take a different API-side validation path. @@ -69,10 +69,10 @@ All APIs that create, update, or delete role assignments (i.e., policies of type The following rules apply initially: - The glob character (``*``) is only supported as a **suffix wildcard**. It cannot appear in the middle of a scope identifier. -- A glob pattern represents a **bounded prefix match** for the external key portion of a scope within its namespace. The API validation ensures the prefix is meaningful (i.e., it corresponds to a valid identifier boundary for that namespace), so the glob cannot be used to accidentally broaden access. -- For any glob patterns (courses, libraries, or future namespaces), malformed inputs (such as mid-string wildcards or prefixes that do not match the expected key format/boundaries) are **rejected**. +- A glob pattern represents a **boundary-constrained prefix match** for the external key portion of a scope within its namespace. In practice, this means matching is limited to a well-defined identifier boundary, and the glob cannot be used to create overly broad or unsafe matches. +- For any glob patterns (courses, libraries, or future namespaces), malformed inputs (such as mid-string wildcards or prefixes that do not match expected key formats or identifier boundaries) are **rejected**. - Additional namespaces must define their own, explicit validation rules before accepting glob scopes. If a namespace does not have a custom matcher/validator, any ``*`` in its scope is rejected and only exact scopes are accepted. -- As needs evolve, more glob types can be added safely by introducing namespace-specific semantics and validations (for example, additional prefix boundaries such as program/tenant prefixes, or narrower matching strategies if required). +- As needs evolve, more glob pattern types can be added safely by introducing namespace-specific semantics and validations (for example, additional prefix boundaries such as program/tenant prefixes, or narrower matching strategies if required). Examples of valid/invalid namespace scope globs ----------------------------------------------- @@ -82,7 +82,7 @@ Given the current suffix-glob policy (single trailing ``*`` at a namespace-appro - Invalid: ``*`` (unbounded across all namespaces/scopes). - Invalid: ``c*`` or ``l*`` (not a valid namespaced scope prefix). - Invalid: ``course-v1*`` or ``lib*`` (missing ``:`` and required namespace structure). -- Invalid: ``course-v1:*`` or ``lib:*`` (wildcard starts before a bounded prefix inside the ``course-v1`` or ``lib`` key). +- Invalid: ``course-v1:*`` or ``lib:*`` (wildcard starts before a valid prefix boundary inside the ``course-v1`` or ``lib`` key). - Invalid: ``course-v1:O*`` or ``lib:MIT*`` (wildcard starts mid-segment. Prefix boundary is not complete). - Valid: ``course-v1:OpenedX+*`` (wildcard starts at an approved boundary for the ``course-v1`` namespace). - Valid: ``lib:MITx:*`` (wildcard starts at an approved boundary for the ``lib`` namespace). @@ -92,11 +92,11 @@ These validation rules are implemented in the Open edX layer (API / data layer), 3. Keep the model general and extensible ======================================== -By introducing glob support in role assignments in a constrained way, we unlock a set of future extensions without redesigning the model: +By introducing glob support in role assignments in a boundary-constrained way, we unlock a set of future extensions without redesigning the model: - **Other scope types** - - Scope types with hierarchical or prefix-based identifiers (for example, libraries or other content groupings) can adopt glob support by: + - Scope types with hierarchical or prefix-based identifiers (for example, libraries or other content groupings) can adopt glob pattern support by: - Defining their own namespace-specific rules for valid suffix globs. - Reusing the same enforcer-level domain matching capability. @@ -114,13 +114,13 @@ Positive consequences - **Increased expressiveness**: Operators can express multi-scope role assignments (for example, "course staff for all courses in organization OpenedX") without enumerating each course in individual ``g`` policies. - **Reduced operational overhead**: New resources that fall under an existing glob pattern automatically inherit the appropriate role assignments, reducing the need for ongoing manual updates. -- **Better alignment with real-world use cases**: Many organizational setups naturally require "all resources under this prefix" semantics. Glob support maps directly to those needs. +- **Better alignment with real-world use cases**: Many organizational setups naturally require "all resources under this prefix" semantics. Glob pattern support maps directly to those needs. - **Clear extension path**: The mechanism is generic enough to be reused for other namespaces (such as organization or library scopes), as long as each namespace defines and enforces its own validation rules. Negative consequences / risks ============================= -- **Security and safety**: If validation is misconfigured or bypassed, glob patterns could unintentionally grant access beyond the intended boundary. This risk is mitigated by: +- **Security and safety**: If validation is misconfigured or bypassed, glob patterns could unintentionally grant access beyond the intended identifier boundary. This risk is mitigated by: - Enforcing validation in the Open edX API layer. - Restricting globs to trailing ``*`` patterns. From 58bd64ed93c7fd6fd50b7fb49e53b362f7cca964 Mon Sep 17 00:00:00 2001 From: Bryann Valderrama Date: Thu, 26 Mar 2026 11:37:00 -0500 Subject: [PATCH 6/6] chore: fix docs failure --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c03c0251..4cb50cf0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -37,9 +37,9 @@ Added ****************** Removed +======= * Dropped support for Python 3.11. -======= 0.23.0 - 2026-02-18 ********************