-
Notifications
You must be signed in to change notification settings - Fork 159
Add functions for inspecting TPE determining policies #2401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
6e677ed
8197a5a
0276383
2da907c
a1b7f39
85ce1f9
a1b0e6a
feccd1a
ce2ef49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,8 +59,8 @@ impl ResidualPolicy { | |
| } | ||
|
|
||
| /// Get the [`PolicyID`] | ||
| pub fn get_policy_id(&self) -> PolicyID { | ||
| self.policy.id().clone() | ||
| pub fn get_policy_id(&self) -> &PolicyID { | ||
| self.policy.id() | ||
| } | ||
|
john-h-kastner-aws marked this conversation as resolved.
|
||
|
|
||
| /// All literal uids referenced by this residual | ||
|
|
@@ -130,20 +130,20 @@ impl<'a> Response<'a> { | |
| match rp.get_effect() { | ||
| Effect::Forbid => { | ||
| if r.is_true() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: feels like this could be a good candidate to have an enum / match statement for in the future.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could write this as a match on the residual, but I don't think I see how it would make the code any nicer |
||
| satisfied_forbids.insert(id); | ||
| satisfied_forbids.insert(id.clone()); | ||
| } else if r.is_false() || r.is_error() { | ||
| false_forbids.insert(id); | ||
| false_forbids.insert(id.clone()); | ||
| } else { | ||
| residual_forbids.insert(id); | ||
| residual_forbids.insert(id.clone()); | ||
| } | ||
| } | ||
| Effect::Permit => { | ||
| if r.is_true() { | ||
| satisfied_permits.insert(id); | ||
| satisfied_permits.insert(id.clone()); | ||
| } else if r.is_false() || r.is_error() { | ||
| false_permits.insert(id); | ||
| false_permits.insert(id.clone()); | ||
| } else { | ||
| residual_permits.insert(id); | ||
| residual_permits.insert(id.clone()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,8 +32,9 @@ use smol_str::SmolStr; | |
|
|
||
| use crate::{ | ||
| api, tpe_err, Authorizer, Context, Entities, Entity, EntityId, EntityTypeName, EntityUid, | ||
| PartialEntityError, PartialRequestCreationError, PermissionQueryError, Policy, PolicySet, | ||
| Request, RequestValidationError, RestrictedExpression, Schema, TpeReauthorizationError, | ||
| PartialEntityError, PartialRequestCreationError, PermissionQueryError, Policy, PolicyId, | ||
| PolicySet, Request, RequestValidationError, RestrictedExpression, Schema, | ||
| TpeReauthorizationError, | ||
| }; | ||
|
|
||
| /// A partial [`EntityUid`]. | ||
|
|
@@ -384,6 +385,44 @@ impl TpeResponse<'_> { | |
| self.0.decision() | ||
| } | ||
|
|
||
| /// Get the determining policies for the partial authorization decision. | ||
| /// These are a subset of the determining policies for any subsequent | ||
| /// concrete reauthorization. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is that if you were to do concrete authorization, there might be more determining policies that aren't represented here. If you want to take some action based on which policies apply to a request, this might be important.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. expanded comments substantially. let me know if it makes sense
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe |
||
| /// | ||
| /// When [`TpeResponse::decision`] returns a concrete allow or deny, the | ||
| /// determining policies are the satisfied permits or forbids respectively. | ||
| /// If TPE did not reach a concrete decision, then this will always return | ||
| /// an empty set. | ||
|
john-h-kastner-aws marked this conversation as resolved.
Outdated
|
||
| pub fn reason(&self) -> HashSet<&PolicyId> { | ||
|
john-h-kastner-aws marked this conversation as resolved.
Outdated
|
||
| match self.0.decision() { | ||
| None => HashSet::new(), | ||
|
john-h-kastner-aws marked this conversation as resolved.
Outdated
|
||
| Some(Decision::Allow) => self.satisfied_permits().collect(), | ||
| Some(Decision::Deny) => self.satisfied_forbids().collect(), | ||
|
john-h-kastner-aws marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
|
|
||
|
john-h-kastner-aws marked this conversation as resolved.
|
||
| /// Get the permit policies that are concretely satisfied by the partial request. | ||
|
luxas marked this conversation as resolved.
|
||
| /// | ||
| /// If the eventual concrete authorization decision is allow, then these | ||
| /// will be a subset of the determining policies. However, they may still be | ||
| /// overridden by a non-trivial residual forbid policy. | ||
|
victornicolet marked this conversation as resolved.
Outdated
|
||
| pub fn satisfied_permits(&self) -> impl Iterator<Item = &PolicyId> { | ||
| self.0 | ||
| .satisfied_permits() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one missing getter rename, or don't we want to update all of these at the same time? |
||
| .map(|rp| PolicyId::ref_cast(rp.get_policy_id())) | ||
| } | ||
|
john-h-kastner-aws marked this conversation as resolved.
|
||
|
|
||
| /// Get the forbid policies that are concretely satisfied by the partial request. | ||
| /// | ||
| /// Presence of any satisfied forbids guarantees that they are exactly the | ||
| /// policies returned by [`TpeResponse::reason`] and that [`TpeResponse::decision`] | ||
| /// must return `Deny`. | ||
| pub fn satisfied_forbids(&self) -> impl Iterator<Item = &PolicyId> { | ||
|
john-h-kastner-aws marked this conversation as resolved.
Outdated
|
||
| self.0 | ||
| .satisfied_forbids() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like one missing getter rename |
||
| .map(|rp| PolicyId::ref_cast(rp.get_policy_id())) | ||
| } | ||
|
|
||
| /// Perform reauthorization | ||
| pub fn reauthorize( | ||
| &self, | ||
|
|
@@ -818,7 +857,10 @@ mod tpe_tests { | |
| } | ||
|
|
||
| mod streaming_service { | ||
| use std::{collections::BTreeMap, str::FromStr}; | ||
| use std::{ | ||
| collections::{BTreeMap, HashSet}, | ||
| str::FromStr, | ||
| }; | ||
|
|
||
| use cedar_policy_core::{authorizer::Decision, tpe::err::EntitiesError}; | ||
| use cool_asserts::assert_matches; | ||
|
|
@@ -1259,6 +1301,7 @@ unless | |
| ); | ||
|
|
||
| assert_eq!(response.decision(), None); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
|
|
||
| let request = Request::new( | ||
| EntityUid::from_type_name_and_id( | ||
|
|
@@ -2115,10 +2158,10 @@ when { principal in resource.admins }; | |
| use itertools::Itertools; | ||
|
|
||
| use crate::{ | ||
| Context, Entities, PartialEntities, PartialEntityUid, PartialRequest, PolicySet, | ||
| PrincipalQueryRequest, ResourceQueryRequest, Schema, | ||
| Context, Entities, PartialEntities, PartialEntityUid, PartialRequest, PolicyId, | ||
| PolicySet, PrincipalQueryRequest, ResourceQueryRequest, Schema, | ||
| }; | ||
| use std::{i64, str::FromStr}; | ||
| use std::{collections::HashSet, i64, str::FromStr}; | ||
|
|
||
| fn schema() -> Schema { | ||
| Schema::from_str("entity P, R; action A appliesTo { principal: P, resource: R };") | ||
|
|
@@ -2153,6 +2196,10 @@ when { principal in resource.admins }; | |
| .tpe(&req, &partial_entities, &schema) | ||
| .unwrap(); | ||
| assert_eq!(response.decision(), Some(Decision::Allow)); | ||
| assert_eq!( | ||
| response.reason(), | ||
| HashSet::from([&PolicyId::new("policy0")]) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2214,6 +2261,10 @@ when { principal in resource.admins }; | |
| .tpe(&req, &partial_entities, &schema) | ||
| .unwrap(); | ||
| assert_eq!(response.decision(), Some(Decision::Deny)); | ||
| assert_eq!( | ||
| response.reason(), | ||
| HashSet::from([&PolicyId::new("policy0")]) | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2278,6 +2329,7 @@ when { principal in resource.admins }; | |
| .tpe(&req, &partial_entities, &schema) | ||
| .unwrap(); | ||
| assert_eq!(response.decision(), Some(Decision::Deny)); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2345,6 +2397,7 @@ when { principal in resource.admins }; | |
| .tpe(&req, &partial_entities, &schema) | ||
| .unwrap(); | ||
| assert_eq!(response.decision(), Some(Decision::Deny)); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2815,7 +2868,10 @@ when { principal in resource.admins }; | |
| } | ||
|
|
||
| mod template_links { | ||
| use std::{collections::HashMap, str::FromStr}; | ||
| use std::{ | ||
| collections::{HashMap, HashSet}, | ||
| str::FromStr, | ||
| }; | ||
|
|
||
| use crate::{ | ||
| pst, Decision, EntityUid, PartialEntities, PartialEntityUid, PartialRequest, Policy, | ||
|
|
@@ -2877,6 +2933,7 @@ when { principal in resource.admins }; | |
| let response = policies.tpe(&request, &es, &schema).unwrap(); | ||
|
|
||
| assert_eq!(response.decision(), Some(Decision::Allow)); | ||
| assert_eq!(response.reason(), HashSet::from([&PolicyId::new("l")])); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2889,6 +2946,7 @@ when { principal in resource.admins }; | |
| let response = policies.tpe(&request, &es, &schema).unwrap(); | ||
|
|
||
| assert_eq!(response.decision(), Some(Decision::Deny)); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2911,6 +2969,7 @@ when { principal in resource.admins }; | |
| let response = policies.tpe(&request, &es, &schema).unwrap(); | ||
|
|
||
| assert_eq!(response.decision(), Some(Decision::Deny)); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -2943,6 +3002,7 @@ when { principal in resource.admins }; | |
| let residuals: Vec<_> = response.nontrivial_residual_policies().collect(); | ||
| assert_eq!(residuals[0].to_pst().unwrap().body(), expected.body()); | ||
| assert_eq!(response.decision(), None); | ||
| assert_eq!(response.reason(), HashSet::new()); | ||
| assert_eq!(residuals.len(), 1); | ||
| } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.