forked from devmonkey22/oso-casemgmt-django
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Refactor policies to use new
base_allow
effectively.
- Add in additional policy rule helpers/layers to simplify rules required per model, etc. - Add performance debug log for AuthorizeFilter (to track how long polar/filter generation takes). - Update policies to use new `PermissionHelper` extension class to look up permission IDs (globally) rather than directly querying through ContentType and Permission models on each query, which adds several extra joins. These can be cached/optimized further as a separate concern. - Add `LOGGING` setting to allow seeing debug logs. - Update README with more info.
- Loading branch information
1 parent
67c85a5
commit 05e7e1d
Showing
13 changed files
with
381 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,49 @@ | ||
# ALLOW RULES --- entrypoint for all authorization decisions | ||
### ######################################################## | ||
### ALLOW RULES --- entrypoint for all authorization decisions | ||
### ######################################################## | ||
|
||
## Defer to models | ||
### Is user allowed to perform "action" on resource? | ||
### | ||
### The ``action`` may be a standard "view", "add", "change", "delete" model permission or a custom | ||
### action that matches "{app_label}.{action}_{model_name}". | ||
### | ||
|
||
### check for RBAC rule | ||
allow(actor, action: String, resource) if | ||
rbac_allow(actor, action, resource); | ||
### Lookup action -> permission, then check allow | ||
action_to_permission(action, resource, perm) and | ||
allow(actor, perm, resource); | ||
|
||
### check for global rule | ||
allow(actor, action: String, resource) if | ||
global_allow(actor, action, resource); | ||
# Each model should define their own allow, and can use `base_allow` and/or any other custom logic. | ||
# We could use this generic rule to automatically call `base_allow`, then I feel like it's more difficult to totally | ||
# override all policies on a per-model basis. | ||
#allow(actor, perm: PermissionInfo, resource) if | ||
# base_allow(actor, perm, resource); | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### ######################################################## | ||
### BASE ALLOW POLICIES | ||
### ######################################################## | ||
|
||
## Delegate by resource | ||
|
||
### Delegate | ||
### User has access if allowed to access resource with given permission (using RBAC with potentially resource-scoped (non-global) roles) | ||
base_allow(actor, perm: PermissionInfo, resource) if | ||
rbac_allow(actor, perm, resource); | ||
|
||
allow(user: casemgmt::User, action, client: casemgmt::Client) if | ||
# NB: All relations should be expressed over the resource on the RHS | ||
# to avoid using querysets | ||
caseload in client.caseloads and | ||
caseload matches casemgmt::Caseload and | ||
allow(user, action, caseload); | ||
|
||
### User has access if has permission through direct (global) permission assignment | ||
base_allow(actor, perm: PermissionInfo, resource) if | ||
global_allow(actor, perm, resource); | ||
|
||
allow(user: casemgmt::User, action, case_type: casemgmt::CaseType) if | ||
# NB: All relations should be expressed over the resource on the RHS | ||
# to avoid using querysets | ||
caseload in case_type.caseloads and | ||
caseload matches casemgmt::Caseload and | ||
allow(user, action, caseload); | ||
|
||
# Superusers can do anything, regardless of permission/action | ||
base_allow(actor, _action, _resource) if | ||
actor.is_superuser; | ||
|
||
allow(user: casemgmt::User, action, template: casemgmt::DocumentTemplate) if | ||
caseload in template.case_type.caseloads and | ||
caseload matches casemgmt::Caseload and | ||
allow(user, action, caseload); | ||
|
||
allow(user: casemgmt::User, action, document: casemgmt::Document) if | ||
caseload in document.template.case_type.caseloads and | ||
caseload in document.client.caseloads and | ||
caseload matches casemgmt::Caseload and | ||
allow(user, action, caseload); | ||
|
||
|
||
# Allow access if user has same action rights on related document | ||
allow(user: casemgmt::User, action, elig_data: casemgmt::WkcmpEligibilityData) if | ||
allow(user, action, elig_data.document); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,11 @@ | ||
## Model-specific rules for caseload roles. | ||
## (Would be autogenerated by oso) | ||
|
||
# User in caseload role (directly) or User in caseload role (as a group member) | ||
user_in_role(user: casemgmt::User, role, resource: casemgmt::Caseload) if | ||
## Note: we are using the related name to go from resource to the m2m model | ||
## This should be preferred so all attributes at written over the resource partial | ||
caseload_role in resource.caseload_roles and | ||
caseload_role.user = user and | ||
role = caseload_role.role; | ||
|
||
### Maps action to `casemgmt.{action}_caseload` | ||
### e.g. "view" -> "casemgmt.view_caseload" | ||
action_to_permission(action, _: casemgmt::Caseload, perm) if | ||
perm = "_".join([action, "caseload"]); | ||
|
||
# Direct role permission assignments | ||
role_allow(role, action, resource) if | ||
action_to_permission(action, resource, permission) and | ||
role_perm in role.permissions and | ||
role_perm.codename = permission and | ||
role_perm.content_type.app_label = "casemgmt" and | ||
role_perm.content_type.model = "caseload"; | ||
|
||
# TODO: Revisit | ||
# inherits_role(role: CaseloadRole, inherited_role) if | ||
# caseload_role_order(role_order) and | ||
# inherits_role_helper(role.name, inherited_role_name, role_order) and | ||
# inherited_role = new CaseloadRole(name: inherited_role_name, caseload: role.caseload); | ||
role = caseload_role.role and | ||
(caseload_role.user = user or | ||
user in caseload_role.group.user); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,14 @@ | ||
## Global permissions assigned to users | ||
|
||
### Allow superusers/staff to view everything | ||
allow(user: casemgmt::User, _action, _resource) if | ||
user.is_staff; | ||
#global_allow(user: casemgmt::User, _action, _resource) if | ||
# user.is_superuser; | ||
|
||
### User has access if has permission through direct permission assignment | ||
global_allow(user: casemgmt::User, action: String, _resource: casemgmt::Caseload) if | ||
user.has_perm("".join(["casemgmt.", action, "_caseload"])); | ||
### User has access if has permission through direct (global) permission assignment | ||
global_allow(user, perm: String, _resource) if | ||
user_has_perm(user, perm); | ||
|
||
global_allow(user: casemgmt::User, action: String, _resource: casemgmt::Document) if | ||
user.has_perm("".join(["casemgmt.", action, "_document"])); | ||
|
||
global_allow(user: casemgmt::User, action: String, _resource: casemgmt::Client) if | ||
user.has_perm("".join(["casemgmt.", action, "_client"])); | ||
|
||
global_allow(user: casemgmt::User, action: String, _resource: casemgmt::CaseType) if | ||
user.has_perm("".join(["casemgmt.", action, "_casetype"])); | ||
### User has access if has permission through direct (global) permission assignment | ||
global_allow(user, perm: PermissionInfo, _resource) if | ||
user_has_perm(user, perm.full_name); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
### ######################################################## | ||
### HELPER RULES | ||
### ######################################################## | ||
|
||
### Maps action to permission info { "id": int, "full_name": "{app_label}.{action}_{model_name}", ... } | ||
### e.g. "view" -> "casemgmt.view_client" | ||
action_to_permission(action: String, resource, perm) if | ||
resource_to_class(resource, resource_cls) and | ||
perm = PermissionHelper.get_permission_info_for_model_action(action, resource_cls); | ||
|
||
|
||
# If action is already PermissionInfo, use it | ||
action_to_permission(action: PermissionInfo, _resource, perm) if | ||
perm = action; | ||
|
||
|
||
|
||
### Does the user have the given global role (permission)? | ||
user_has_perm(user, perm: String) if | ||
user.has_perm(perm); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
### ######################################################## | ||
### MODEL ALLOW POLICIES | ||
### | ||
### If a model wants to perform any additional ABAC type checks, they should be able to add those into the `allow` rules | ||
### below, or defer into a lower-level rule like `role_allow` or something. | ||
### ######################################################## | ||
|
||
|
||
|
||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::Client) if | ||
base_allow(user, perm, resource); | ||
|
||
|
||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::CaseType) if | ||
base_allow(user, perm, resource); | ||
|
||
|
||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::DocumentTemplate) if | ||
base_allow(user, perm, resource); | ||
|
||
|
||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::Document) if | ||
base_allow(user, perm, resource); | ||
|
||
|
||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::Caseload) if | ||
base_allow(user, perm, resource); | ||
|
||
|
||
# Allow access if user has permission using related document as a potential role source | ||
# The permissions should still be `casemgmt.{action}_wkcmpeligibilitydata`, etc so role needs those permissions still. | ||
allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::WkcmpEligibilityData) if | ||
allow(user, perm, resource.document); | ||
|
||
|
||
# Alternately, for WkcmpEligibilityData, we could have just called `base_allow`, and defined the | ||
# `resource_role_applies_to(elig_data: casemgmt::WkcmpEligibilityData, resource)` rule below. The above `allow` | ||
# approach allows us to reuse/call document-specific policies more directly. | ||
# | ||
# allow(user: casemgmt::User, perm: PermissionInfo, resource: casemgmt::WkcmpEligibilityData) if | ||
# base_allow(user, perm, resource); | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### #################################################################################################### | ||
### RELATIONSHIP RULES/HELPERS | ||
|
||
# TODO: If we had a way to use a function like `type(resource)` to pull the partial's type automatically, | ||
# these rules wouldn't be needed, or could become generic like: | ||
# resource_to_class(resource, resource_cls) if | ||
# resource_cls = type(resource); | ||
# | ||
# Define the link between an instance of a resource (even a Partial), and the real type | ||
resource_to_class(_resource: casemgmt::Caseload, casemgmt::Caseload); | ||
resource_to_class(_resource: casemgmt::Client, casemgmt::Client); | ||
resource_to_class(_resource: casemgmt::Document, casemgmt::Document); | ||
resource_to_class(_resource: casemgmt::DocumentTemplate, casemgmt::DocumentTemplate); | ||
resource_to_class(_resource: casemgmt::CaseType, casemgmt::CaseType); | ||
|
||
# Caseloads are their own role sources/relation | ||
resource_role_applies_to(caseload: casemgmt::Caseload, caseload); | ||
|
||
|
||
# Clients are associated to caseloads (and their roles) | ||
resource_role_applies_to(client: casemgmt::Client, caseload) if | ||
caseload in client.caseloads; | ||
|
||
|
||
# CaseTypes are associated to caseloads (and their member roles) | ||
resource_role_applies_to(case_type: casemgmt::CaseType, caseload) if | ||
caseload in case_type.caseloads; | ||
|
||
|
||
# DocumentTemplates are associated to their case type's caseloads (or other things if CaseType defines it) | ||
resource_role_applies_to(template: casemgmt::DocumentTemplate, resource) if | ||
#resource in template.case_type.caseloads; | ||
resource_role_applies_to(template.case_type, resource); | ||
|
||
|
||
|
||
# Documents are associated to caseloads (and their roles) when it's client and case_type are in the same caseload | ||
resource_role_applies_to(resource: casemgmt::Document, caseload) if | ||
caseload in resource.client.caseloads and | ||
caseload in resource.template.case_type.caseloads; | ||
|
||
|
||
# Eligibility Data is associated to it's document's role relations (ie: should be a caseload) | ||
#resource_role_applies_to(elig_data: casemgmt::WkcmpEligibilityData, resource) if | ||
# resource_role_applies_to(elig_data.document, resource); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,47 @@ | ||
# RBAC BASE POLICY | ||
### ######################################################## | ||
### RBAC BASE POLICY | ||
### ######################################################## | ||
|
||
## Top-level RBAC allow rule | ||
|
||
### The association between the resource roles and the requested resource is outsourced from the rbac_allow | ||
rbac_allow(user, action, resource) if | ||
# First, check whether user has a direct role | ||
# or a role from an associated resource | ||
rbac_allow(user, perm: PermissionInfo, resource) if | ||
# First, check whether user has a direct role or a role from an associated resource | ||
resource_role_applies_to(resource, role_resource) and | ||
user_in_role(user, role, role_resource) and | ||
role_allow(role, action, resource); | ||
role_allow(role, perm, resource); | ||
|
||
# RESOURCE-ROLE RELATIONSHIPS | ||
|
||
## These rules allow roles to apply to resources other than those that they are scoped to. | ||
## The most common example of this is nested resources, e.g. Repository roles should apply to the Issues | ||
## nested in that repository. | ||
|
||
### A resource's roles applies to itself | ||
resource_role_applies_to(role_resource, role_resource); | ||
|
||
# ROLE-ROLE RELATIONSHIPS | ||
### ############################################################################################# | ||
### RESOURCE TO TYPE RELATIONSHIPS | ||
### ############################################################################################# | ||
|
||
## Role Hierarchies | ||
### Each model (resource) that needs to be able to use `action_to_permission()` to convert action to permission | ||
### must define a `resource_to_class()` rule. This is needed until there is support for a function like `type(var)` | ||
### to do this automatically. | ||
|
||
### Grant a role permissions that it inherits from a more junior role | ||
role_allow(role, action, resource) if | ||
inherits_role(role, inherited_role) and | ||
role_allow(inherited_role, action, resource); | ||
#resource_to_class(_resource: label::Name, label::Name) | ||
|
||
# TODO: Revisit if this works | ||
# ### Helper to determine relative order or roles in a list | ||
# inherits_role_helper(role, inherited_role, role_order) if | ||
# ([first, *rest] = role_order and | ||
# role = first and | ||
# inherited_role in rest) or | ||
# ([first, *rest] = role_order and | ||
# inherits_role_helper(role, inherited_role, rest)); | ||
|
||
|
||
### ######################################################## | ||
### ROLE ALLOW CHECKS | ||
### ######################################################## | ||
|
||
|
||
### Direct/indirect `Role` permission check | ||
### If role has given permission by ID | ||
role_allow(role, perm: PermissionInfo, _resource) if | ||
role_perm in role.permissions and | ||
role_perm.id = perm.id; | ||
|
||
|
||
### Direct/indirect `Role` permission assignments | ||
### If role has given permission name | ||
role_allow(role, perm: String, _resource) if | ||
# Lookup permission info first | ||
perm_info = PermissionHelper.get_permission_info(perm) and | ||
role_perm in role.permissions and | ||
role_perm.id = perm_info.id; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Register Oso extensions, etc | ||
from casemgmt_example import auth | ||
auth.register_extensions() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from django_oso import Oso | ||
|
||
from . import oso_extensions | ||
|
||
|
||
def register_extensions(): | ||
# Register extensions/types into Oso | ||
Oso.register_constant(oso_extensions.PermissionHelpers, name="PermissionHelper") | ||
Oso.register_class(oso_extensions.PermissionInfo, name="PermissionInfo") |
Oops, something went wrong.