Conversation
|
Thanks for the pull request, @rodmgwgu! This repository is currently maintained by 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 approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo 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:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere 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:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
2b7dda1 to
912e202
Compare
28f621d to
225bbb4
Compare
afd6ef7 to
7cf22fa
Compare
carlos-marquez-wgu
left a comment
There was a problem hiding this comment.
LGTM, thanks!
7cf22fa to
a1496aa
Compare
a1496aa to
dd31512
Compare
mariajgrimaldi
left a comment
There was a problem hiding this comment.
I left a few comments that I think could apply more broadly across the PR, but I didn’t want to repeat the same thing in multiple places. Let me know what you think!
| enforcer = self._resolve_enforcer() | ||
|
|
||
| rendered_set = set(rendered.rows) | ||
| stored_set = {PolicyRow.from_policy(row) for row in enforcer.get_policy()} | ||
| managed_set = self._managed_rows() |
There was a problem hiding this comment.
Can we use API methods instead of handling the policy directly?
| from django.db import transaction # pylint: disable=import-outside-toplevel | ||
|
|
||
| from openedx_authz.engine.enforcer import AuthzEnforcer # pylint: disable=import-outside-toplevel |
There was a problem hiding this comment.
Is this import in this level necessary?
| with transaction.atomic(): | ||
| for row in plan.added_rows: | ||
| enforcer.add_policy(*row.as_policy()) | ||
| for row in plan.removed_rows: | ||
| enforcer.remove_policy(*row.as_policy()) | ||
| removed_assignments: list[tuple[str, str, str]] = [] | ||
| if force and plan.blocking_assignments: | ||
| removed_assignments = self._remove_assignments(enforcer, plan.blocking_assignments) | ||
| self._store_sources(schema) | ||
| # Emit the audit events only if the transaction commits, mirroring | ||
| # unassign_role_from_subject_in_scope, so no audit row is written for | ||
| # an assignment removal that gets rolled back. | ||
| if removed_assignments: | ||
| transaction.on_commit(lambda: self._emit_assignment_deleted(removed_assignments)) |
There was a problem hiding this comment.
Same question here about not using casbin methods but instead low-level APIs? So we encapsulate getting the enforcer and so on.
| except Exception: | ||
| # Runs outside the rolled-back block, so the new version commits. | ||
| AuthzEnforcer.invalidate_policy_cache() | ||
| logger.exception("Authz schema apply failed; policy cache invalidated to force a reload.") | ||
| raise |
There was a problem hiding this comment.
I also don't think we should do the cache invalidation manually
Problem
Rendered rows have to reach the database without damaging anything the schema loader does not own. The apply step shares tables with dynamic roles, user assignments and the legacy
g2action-inheritance rows, so it needs a change report before any write, precise ownership-scoped removal, and a single transaction (ADR 0018 §2, §3, §6, ADR 0025 §6).Approach
The rest of
openedx_authz/engine/renderer.py, on top of the render half in #479.SchemaApplier.plan()— the change report:prows to add or remove, plus definition-level diffs including metadata-only edits that change no policy row at allSchemaApplier.apply()— one transaction: add missing rows, remove stale rows it owns, prune definition/source records the compiled schema no longer contains, invalidate the policy cacheChangePlan,DefinitionDiff,ApplyResultopenedx_authz/tests/schema/{test_apply,test_source_storage}.py,openedx_authz/tests/integration/test_schema_apply.pyThree properties worth reviewing directly:
prows, dynamic roles, user assignments and legacyg2rows survive.load_policiesadds zero rows.RoleAssignmentAuditrecord is written per removed assignment.Manual testing instructions
The integration module is excluded from the default pytest run and needs an edx-platform environment. It exercises the real Casbin enforcer and ORM, including
enforce()assertions for permission removal, force-removal, adoption and failure rollback:tutor dev run cms pytest -p no:randomly --create-db --ds=cms.envs.test \ /mnt/openedx-authz/openedx_authz/tests/integration/test_schema_apply.pyEnd-to-end exercise through the management command is in 8/8, which is the only thing that calls the applier.
Rollback plan
Revert this PR. Nothing invokes the applier until the command lands in 8/8, so the revert is inert. If it has already run, the definition tables it populated are additive and the Casbin rows it wrote match what
authz.policyalready defines.Retro compatibility
No authorization behavior changes. Loading
authz.policyworks as before, and no code path reaches the applier until the management command lands.AI Usage
Kiro was used to assist on feature planning and implementation. Implementation was done step by step with human guidance and validation, based on the ADRs.
Stack (7/8) — #446 split into reviewable pieces. Bases chain bottom-up; merge in order.
rod/authz-schema-renderer)load_authz_schemacommand, version bump and changelogMerge checklist: