Skip to content

feat: add org glob support#225

Open
BryanttV wants to merge 32 commits intoopenedx:mainfrom
eduNEXT:bav/org-glob-support
Open

feat: add org glob support#225
BryanttV wants to merge 32 commits intoopenedx:mainfrom
eduNEXT:bav/org-glob-support

Conversation

@BryanttV
Copy link
Contributor

@BryanttV BryanttV commented Mar 12, 2026

Closes: #172

Description

This PR implements organization-level glob pattern matching for role assignments in the Casbin enforcer, enabling administrators to assign roles across all courses or libraries within a specific organization using a single policy rule.

Previously, the AuthZ system only supported:

  • Exact scope matching (e.g., lib:WGU:CSPROB or course-v1:OpenedX+DemoX+DemoCourse)

Now, administrators can assign roles using organization-level glob patterns:

  • Library scope: lib:DemoX:* (matches all libraries in DemoX org)
  • Course scope: course-v1:OpenedX+* (matches all courses in OpenedX org)

Changes

Core Functionality

  • Domain Matching Function: Added key_match_func to the Casbin enforcer to enable glob-like pattern matching at the end of scope strings

    • Modified AuthzEnforcer._initialize_enforcer() to register the matching function via enforcer.add_named_domain_matching_func("g", key_match_func)
    • This allows the "g" (grouping) function in the Casbin model to match patterns like course-v1:OpenedX+*
  • Validation Rules:

    • Allowed: course-v1:OpenedX+*, lib:DemoX:* (org-level wildcards)
    • Blocked: course-v1* (wildcard before org)
    • Blocked: course-v1:* (wildcard without org prefix)
    • Blocked: course-v1:OpenedX+Course* (wildcard at course level)
    • Blocked: lib:DemoX:Slug* (wildcard at slug level)
    • Organization existence is verified to prevent overly broad permissions

Example Use Case

Before (required one role assignment per course):

assign_role_to_user_in_scope(user, role, "course-v1:WGU+COURSE1+2026")
assign_role_to_user_in_scope(user, role, "course-v1:WGU+COURSE2+2026")
assign_role_to_user_in_scope(user, role, "course-v1:WGU+COURSE3+2026")
# ... hundreds of assignments for all WGU courses

After (single org-level assignment):

assign_role_to_user_in_scope(user, role, "course-v1:WGU+*")

Permission Check:

# All these now return True with a single org-level assignment
is_user_allowed("staff-user", "courses.manage_advanced_settings", "course-v1:WGU+COURSE1+2026")
is_user_allowed("staff-user", "courses.manage_advanced_settings", "course-v1:WGU+COURSE2+2026")
is_user_allowed("staff-user", "courses.manage_advanced_settings", "course-v1:WGU+ANYOTHER+2026")

Technical Details

Casbin Model Behavior

The key_match_func function from casbin.util enables pattern matching where:

  • course-v1:OpenedX+* matches any string starting with course-v1:OpenedX+
  • lib:DemoX:* matches any string starting with lib:DemoX:

This is applied to the domain (scope) parameter of the "g" grouping function in the Casbin model, allowing role assignments with glob patterns to match concrete resource scopes during permission checks.

Glob Scope Base Class

OrgGlobData is the base class for organization-scoped glob scope keys.
It represents organization-wide patterns of the form:

  • {NAMESPACE}:{ORG}{ID_SEPARATOR}*

Concrete glob scope types such as OrgContentLibraryGlobData and OrgCourseOverviewGlobData inherit from OrgGlobData and specialize it for their respective namespaces.

Glob Scope Data Classes

  • OrgContentLibraryGlobData (lib:ORG:*): New scope type representing “all libraries in organization ORG”.
  • OrgCourseOverviewGlobData (course-v1:ORG+*): New scope type representing “all courses in organization ORG”.

Security Considerations

  • Organization existence validation prevents creation of overly broad permissions for non-existent organizations
  • Only org-level globs are permitted, preventing unintended permission escalation through course/run/slug wildcards
  • Validation occurs at role assignment time, failing fast with clear error messages

Testing Instructions

To test these changes, you can check the REST API. You can download this open-edx-authz.postman_collection.json, OR go to {lms_domain}/api-docs/#/authz OR use the DRF UI from the browser.

Test Plan

  • Unit tests for validation module (all scenarios covered)
  • Unit tests for enforcement with org-level globs
  • Verify valid glob patterns are accepted
  • Verify invalid glob patterns raise appropriate errors
  • Verify organization existence checks work correctly
  • Verify glob patterns match concrete scopes during permission checks
  • Verify existing functionality (exact matches, global wildcards) still works

Related Documentation

Merge checklist

Check off if complete or not applicable:

  • Version bumped
  • Changelog record added
  • Documentation updated (not only docstrings)
  • Fixup commits are squashed away
  • Unit tests added/updated
  • Manual testing instructions provided
  • Noted any: Concerns, dependencies, migration issues, deadlines, tickets

@openedx-webhooks openedx-webhooks added open-source-contribution PR author is not from Axim or 2U core contributor PR author is a Core Contributor (who may or may not have write access to this repo). labels Mar 12, 2026
@openedx-webhooks
Copy link

openedx-webhooks commented Mar 12, 2026

Thanks for the pull request, @BryanttV!

This repository is currently maintained by @openedx/committers-openedx-authz.

Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review.

🔘 Get product approval

If you haven't already, check this list to see if your contribution needs to go through the product review process.

  • If it does, you'll need to submit a product proposal for your contribution, and have it reviewed by the Product Working Group.
    • This process (including the steps you'll need to take) is documented here.
  • If it doesn't, simply proceed with the next step.
🔘 Provide context

To help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:

  • Dependencies

    This PR must be merged before / after / at the same time as ...

  • Blockers

    This PR is waiting for OEP-1234 to be accepted.

  • Timeline information

    This PR must be merged by XX date because ...

  • Partner information

    This is for a course on edx.org.

  • Supporting documentation
  • Relevant Open edX discussion forum threads
🔘 Get a green build

If one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green.

Details
Where can I find more information?

If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources:

When can I expect my changes to be merged?

Our goal is to get community contributions seen and reviewed as efficiently as possible.

However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:

  • The size and impact of the changes that it introduces
  • The need for product review
  • Maintenance status of the parent repository

💡 As a result it may take up to several weeks or months to complete a review and merge your PR.

@github-project-automation github-project-automation bot moved this to Needs Triage in Contributions Mar 12, 2026
@BryanttV BryanttV marked this pull request as ready for review March 13, 2026 16:48
Copy link
Member

@mariajgrimaldi mariajgrimaldi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this! I haven't tested this so this first review is only a static scan of the code. My main concern is how to maintain the extensibility we've built so far with multiple classes, and whether we could leverage that to include this (globs) as a new scope. Curious to know what you think!

@BryanttV
Copy link
Contributor Author

Hi @mariajgrimaldi, thanks for the review!

My main concern is how to maintain the extensibility we've built so far with multiple classes, and whether we could leverage that to include this (globs) as a new scope. Curious to know what you think!

I definitely agree with your approach. I think we could handle new scopes specifically at the glob level, for example, in this case, something like OrgLevelLibraryGlob and OrgLevelCourseGlob. What do you think? Or would it be better to have a single GlobScope class that handles all validations? I’ll review that refactoring and let you know how it goes.

@rodmgwgu
Copy link
Contributor

Hi @mariajgrimaldi, thanks for the review!

My main concern is how to maintain the extensibility we've built so far with multiple classes, and whether we could leverage that to include this (globs) as a new scope. Curious to know what you think!

I definitely agree with your approach. I think we could handle new scopes specifically at the glob level, for example, in this case, something like OrgLevelLibraryGlob and OrgLevelCourseGlob. What do you think? Or would it be better to have a single GlobScope class that handles all validations? I’ll review that refactoring and let you know how it goes.

To me, it sounds like having 2 separate glob classes would be better (OrgLevelLibraryGlob and OrgLevelCourseGlob), for example, sometimes we may need to check if an assignation is for either libs or courses, having classes for this would make the validation easier.

@BryanttV BryanttV force-pushed the bav/org-glob-support branch 2 times, most recently from 41f4c49 to c8e02fa Compare March 13, 2026 23:24
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from c8e02fa to f7a21b9 Compare March 13, 2026 23:27
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from 7f23636 to 8f49024 Compare March 16, 2026 15:27
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from 8f49024 to a08f22c Compare March 16, 2026 18:11
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from 89615f8 to 7c0d757 Compare March 16, 2026 18:24
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from 793d325 to f4dfa7e Compare March 16, 2026 20:03
@BryanttV
Copy link
Contributor Author

@mariajgrimaldi @rodmgwgu, thank you very much for the review! I’ve just made the final changes according to your valuable suggestions. A new base class (OrgGlobData) was added, which defines all the common methods reused by OrgContentLibraryGlobData and OrgCourseOverviewGlobData. I’ll be deploying these changes remotely and will let you know when it’s ready!

@BryanttV BryanttV requested a review from mariajgrimaldi March 17, 2026 21:38
namespace, external_key = namespaced_key.split(AUTHZ_POLICY_ATTRIBUTES_SEPARATOR, 1)

# Check if this is a glob pattern (contains wildcard)
is_glob = GLOBAL_SCOPE_WILDCARD in external_key
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying!

if is_glob:
# Try to get glob-specific class first
glob_subclass = mcs.glob_registry.get(namespace)
if glob_subclass and glob_subclass.validate_external_key(external_key):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it's somehow invalid? L325 raises an error but here it will break I think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for a subclass to be invalid for globs but valid for exact matches? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if it's somehow invalid? L325 raises an error but here it will break I think

It won’t break here.

If is_glob is true and the glob subclass validation fails, the code intentionally falls back to the standard scope subclass and runs its validate_external_key. If that also fails, we raise ValueError (invalid format).
So the failure path is still controlled.

Is it possible for a subclass to be invalid for globs but valid for exact matches? 🤔

Mmm, in theory, yes, a glob-invalid key could be exact-valid if a namespace’s exact validator allows *.
In our current lib / course-v1 implementations, exact validators do not accept wildcard keys, so it still ends in ValueError.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If is_glob is true and the glob subclass validation fails, the code intentionally falls back to the standard scope subclass and runs its validate_external_key. If that also fails, we raise ValueError (invalid format).
So the failure path is still controlled.

Mmmm but the error would be a bit misleading, no? Asking for a format it doesn't correspond with glob classes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're right. I included new exceptions specifically for glob scopes and updated the tests: 23f340e

Copy link
Contributor

@rodmgwgu rodmgwgu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested this in my local by calling functions manually in the CLI, it's looking good, it behaved as expected.

This is what I ran:

from openedx_authz.api import assign_role_to_user_in_scope, unassign_role_from_user, get_user_role_assignments, is_user_allowed

# Courses
assign_role_to_user_in_scope('contributor', 'course_staff', 'course-v1:OpenedX+*') # Success

is_user_allowed('contributor',  'courses.manage_advanced_settings', 'course-v1:OpenedX+DemoX+DemoCourse') # True

is_user_allowed('contributor',  'courses.manage_advanced_settings', 'course-v1:OpenedX+DemoX+OtherCourse') # True

is_user_allowed('contributor',  'courses.manage_advanced_settings', 'course-v1:OpenedX2+DemoX+DemoCourse') # False

assign_role_to_user_in_scope('contributor', 'course_staff', 'course-v1:OpenedX+**') # Failed as expected

assign_role_to_user_in_scope('contributor', 'course_staff', 'course-v1:OpenedX*') # Failed as expected

# Libraries
assign_role_to_user_in_scope('contributor', 'library_contributor', 'lib:WGU:*') # Success

is_user_allowed('contributor',  'content_libraries.view_library', 'lib:WGU:CSPROB') # True

is_user_allowed('contributor',  'content_libraries.view_library', 'lib:WGU:OTHER') # True

is_user_allowed('contributor',  'content_libraries.view_library', 'lib:WGU2:CSPROB') # False

assign_role_to_user_in_scope('contributor', 'library_contributor', 'lib:WGU:**') # Failed as expected

assign_role_to_user_in_scope('contributor', 'library_contributor', 'lib:WGU*') # Failed as expected

@BryanttV BryanttV force-pushed the bav/org-glob-support branch from e1fd890 to 3e7b441 Compare March 20, 2026 00:05
@BryanttV BryanttV force-pushed the bav/org-glob-support branch from d2e8055 to d3d4238 Compare March 24, 2026 14:23
Copy link
Contributor

@rodmgwgu rodmgwgu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks!

Copy link
Contributor

@bmtcril bmtcril left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks for all of the work on this!

Copy link
Member

@mariajgrimaldi mariajgrimaldi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks a lot :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core contributor PR author is a Core Contributor (who may or may not have write access to this repo). open-source-contribution PR author is not from Axim or 2U

Projects

Status: In Eng Review

Development

Successfully merging this pull request may close these issues.

Task - RBAC AuthZ - Multi scope roles: Implement support for glob permissions and validations

6 participants