Skip to content

fix: apply openedx-authz manage_tags orgs consistently across tagging rules - #39197

Open
BryanttV wants to merge 9 commits into
openedx:masterfrom
eduNEXT:bav/taxonomy-org-view-authz
Open

BryanttV wants to merge 9 commits into
openedx:masterfrom
eduNEXT:bav/taxonomy-org-view-authz

Conversation

@BryanttV

@BryanttV BryanttV commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Description

Builds on #39159, which made UserOrgFilterBackend recognize openedx-authz courses.manage_tags roles so authz-only users (e.g. course_editor, no legacy CourseAccessRole) can list their org's taxonomies. That change only widened the list filter, so the rest of the tagging rules still resolved orgs through legacy roles alone. This PR makes the authz lookup part of the shared org resolution:

  • Single source of truth. The authz lookup is folded into get_user_orgs via _get_authz_manage_tags_orgs, so the list filters, is_org_user and can_view_taxonomy all agree. Previously, an authz-only user could list an org's taxonomies but got a 403 when opening them. The duplicated set union in UserOrgFilterBackend is dropped.
  • Glob scopes are no longer dropped. Org-wide and platform-wide course glob scopes are now counted. They have no single course to toggle, so they are checked against the global authz flag.
  • Fewer flag lookups. The per-course flag lookup is skipped once an org is already resolved.
  • Legacy roles are ignored once a course switches to authz. Legacy course roles in courses that have switched to openedx-authz no longer grant org membership, so visibility matches can_change_object_tag_objectid, which requires courses.manage_tags there.

The legacy path is unchanged for Content Libraries and for courses where the authz toggle is off. The authz lookup is additive on top of it.

Impacted roles: Course Author / Course Staff / Course Admin using Studio taxonomies and tagging, and Operators using the API directly.

Supporting information

Testing instructions

  1. With a course switched to openedx-authz and a user holding only an authz role with courses.manage_tags (e.g. course_editor) and no legacy role:
    • GET /api/content_tagging/v1/taxonomies/?org=<org> lists the org's taxonomies and the global ones.
    • Opening one of those taxonomies returns 200 instead of 403.
  2. With the course not switched to authz, confirm that an authz policy alone grants nothing, and that legacy roles still work as before.
  3. With the course switched to authz, confirm that a user holding only a legacy role on that course no longer gets org membership.

@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 Oct 6, 2026
@openedx-webhooks

openedx-webhooks commented Oct 6, 2026 •

Copy link
Copy Markdown

Thanks for the pull request, @BryanttV!

This repository is currently maintained by @openedx/wg-maintenance-openedx-platform-oncall.

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.

@rodmgwgu rodmgwgu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code is looking good, added a couple of nits and comments. Thanks!

return CourseOverviewData(external_key=str(course_key))

@override_waffle_flag(AUTHZ_COURSE_AUTHORING_FLAG, active=True)
def test_course_scope_grants_its_org(self, mock_get_scopes):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: can we add docstrings to all the tests to keep consistency please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done! 546b7a5

key = str(course_id)
if key not in authz_course_cache:
try:
authz_course_cache[key] = enable_authz_course_authoring(CourseKey.from_string(key))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I had the impression that waffle flags were already cached, do you know if that would also be the case here when querying for a specific course?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You're right, thanks for catching that. WaffleFlagCourseOverrideModel.override_value is already @request_cached, Removed it in: 2618148

@rodmgwgu rodmgwgu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested manually against a my local env. All three testing-instruction cases pass:

  1. authz-only course_editor, course switched — GET /taxonomies/?org=OpenedX → 200, lists the org + global taxonomies; opening a taxonomy detail → 200 (was 403 before this PR). ✅
  2. course not switched — the authz grant alone returns nothing (empty list, detail denied), and a legacy CourseStaffRole still lists and opens as before. ✅
  3. course switched, legacy role only — a user with only a legacy CourseAccessRole and no courses.manage_tags no longer gets org membership (empty list, detail denied). ✅

One note: a denied taxonomy detail returns 404, not 403 as the testing instructions say — the view filters the queryset by get_user_orgs, so an invisible taxonomy is simply absent and DRF returns 404. The access behavior is correct; only the status code differs from the description.

@BryanttV
BryanttV requested a review from efortish October 13, 2026 17:11
@BryanttV
BryanttV force-pushed the bav/taxonomy-org-view-authz branch from aa780cc to 4acbaa1 Compare October 13, 2026 17:32
Comment on lines +112 to +113
access_role.org == org_name and
not (access_role.course_id and course_uses_authz(access_role.course_id))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How is this change related to this PR? Are we adding any tests related to this update? I couldn't find other references to user_has_role_ignore_course_id

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This change is what makes legacy roles consistent with the authz switch. Once a course has switched to openedx-authz, tagging an object in it requires courses.manage_tags (see can_change_object_tag_objectid), so a legacy CourseAccessRole there is no longer authoritative. Before this change, such a user still got org membership through _get_course_user_orgs, so they could list and open the org's taxonomies but then couldn't tag anything in that course.

Comment on lines +170 to +193
scopes = rules_cache.get_authz_manage_tags_scopes(user)

org_names = set()
all_orgs = False
glob_authz_enabled = None
for scope in scopes:
if isinstance(scope, CourseOverviewData):
# Skip the per-course flag lookup when the org is already known.
if scope.org not in org_names and enable_authz_course_authoring(scope.course_key):
org_names.add(scope.org)
elif isinstance(scope, OrgCourseOverviewGlobData | PlatformCourseOverviewGlobData):
if glob_authz_enabled is None:
glob_authz_enabled = enable_authz_course_authoring()
if not glob_authz_enabled:
continue
if isinstance(scope, PlatformCourseOverviewGlobData):
all_orgs = True
break
if scope.org:
org_names.add(scope.org)

if all_orgs:
return list(orgs)
return [org for org in orgs if org.short_name in org_names]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we extract or reuse the scope-expansion logic already implemented in _get_course_keys_from_scopes? It already resolves course, organization, and platform-wide Authz scopes into concrete course keys.

I think get_authz_manage_tags_scopes needs similar behavior, so sharing this logic would keep the scope semantics consistent and avoid implementing the same expansion twice. We may need to generalize it if the tagging case also covers library scopes. What do you think?

Similar comment for get_authz_manage_tags_scopes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call, thanks! I extracted the expansion into a shared helper (get_course_keys_from_scopes) and _get_course_keys_from_scopes in cms now uses it. The tagging rules reuse it too. get_authz_manage_tags_scopes became get_authz_manage_tags_org_names; it expands the user's courses.manage_tags scopes (course, org, and platform) with the helper and caches the resulting orgs per request.

efortish and others added 8 commits October 15, 2026 11:31
TaxonomyOrgView filtered taxonomies using only legacy role resolution
(get_admin_orgs/get_user_orgs), so a user holding only an openedx-authz
role like course_editor, with no legacy CourseAccessRole, got an empty
org list and hit queryset.none(), blocking them from seeing even global
taxonomies.

Add get_authz_manage_tags_orgs, which resolves the orgs where the user
holds courses.manage_tags via get_scopes_for_user_and_permission, and
OR it into UserOrgFilterBackend's existing user_orgs set alongside the
legacy roles, the same additive pattern already used for object tags in
ObjectTagTaxonomyOrgFilterBackend. A scope only counts once its course
has actually switched to openedx-authz, so a policy assignment ahead of
that course's own toggle doesn't grant early access.

Closes openedx-authz#448
… rules

The previous commit only widened UserOrgFilterBackend, so a user with an
authz-only role (e.g. course_editor) could list an org's taxonomies but
got 403 opening them, because can_view_taxonomy still resolved orgs
through legacy roles alone.

Fold the authz lookup into get_user_orgs via _get_authz_manage_tags_orgs,
so the list filters, is_org_user and can_view_taxonomy share one source,
and drop the duplicated set-union in UserOrgFilterBackend.

- Count org-wide and platform-wide course glob scopes, which were
  silently dropped; they are checked against the global authz flag as
  they have no single course to toggle.
- Skip the per-course flag lookup once an org is already resolved.
- Ignore legacy course roles in courses that have switched to
  openedx-authz, so visibility matches can_change_object_tag_objectid,
  which requires courses.manage_tags there.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add TestRulesAuthzManageTagsOrgs for get_user_orgs and can_view_taxonomy:
course, org-glob and platform-glob scopes, the course toggle gating,
a single flag lookup per org, authz-only users viewing their org's
taxonomies, and legacy course roles being ignored once a course has
switched to openedx-authz.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ules

get_user_orgs is evaluated once per taxonomy, and each call re-queried the
authz policy store for the user's courses.manage_tags scopes. Cache the
scopes in the tagging rules request cache and update the query counts
asserted in the content_tagging view tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Course waffle flag overrides are already request-cached, so the local
dict in _get_course_user_orgs added nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…angoapps.authz

Make the logic of _get_course_keys_from_scopes reusable outside of cms
so other apps (e.g. content_tagging) can share the same scope semantics.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Resolve courses.manage_tags scopes with get_course_keys_from_scopes instead of
a bespoke expansion, so tagging follows the same course, org and platform
scope semantics as the rest of the authz-aware code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@BryanttV
BryanttV force-pushed the bav/taxonomy-org-view-authz branch from 4acbaa1 to 0f43c71 Compare October 15, 2026 18:30
CourseOverviewFactory defaults the org column to "edX" regardless of the id,
so org-wide scope expansion, which filters on that column, found no courses.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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: Needs Triage

Development

Successfully merging this pull request may close these issues.

Make TaxonomyOrgView authz-aware to allow access for users with courses.manage_tags permission

5 participants