Skip to content

H-4345: Prepare evaluation of policies when calling from API #6880

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

Merged

Conversation

TimDiekmann
Copy link
Member

🌟 What is the purpose of this PR?

To allow the usage of the new policies we need to bubble up the interface to the Graph API layer. Also, it has to be possible to create a principal context which can be used in Cedar.

🔍 What does this change?

  • Implement a PrincipalStore
  • Expose PrincipalStore up to the rest module (but with commented out functionality to not interfere with the current permission system)
  • Implement creation of principal context creation
  • Greatly simplify handling of actions by creating a list of parents in Rust.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

🛡 What tests cover this?

  • Previous tests were adjusted to use the new create_web function signature
  • A seeding logic was added to tests
  • A new test was added to verify consistency of the Cedar schema and the Rust implementation

@TimDiekmann TimDiekmann requested review from hashdotai and Copilot April 7, 2025 17:09
@TimDiekmann TimDiekmann self-assigned this Apr 7, 2025
@github-actions github-actions bot added area/deps Relates to third-party dependencies (area) area/apps > hash* Affects HASH (a `hash-*` app) area/libs Relates to first-party libraries/crates/packages (area) type/eng > backend Owned by the @backend team area/tests New or updated tests area/apps area/apps > hash-graph labels Apr 7, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 28 out of 29 changed files in this pull request and generated no comments.

Files not reviewed (1)
  • libs/@local/graph/authorization/schemas/policies.cedarschema: Language not supported
Comments suppressed due to low confidence (1)

libs/@local/graph/postgres-store/src/permissions/mod.rs:1004

  • The new 'register_action' implementation removes the explicit parent parameter and relies on 'action.parent()'. Please verify that actions (other than the root 'All') which do not explicitly provide a parent are correctly handled and that an error is raised when an action lacks a defined parent.
pub async fn register_action(&mut self, action: ActionName) -> Result<(), Report<ActionError>> {

Copy link
Member

@hashdotai hashdotai left a comment

Choose a reason for hiding this comment

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

This PR successfully implements the groundwork for using Cedar policies for permission evaluation. The implementation creates a solid foundation by making several key changes:

  1. Action hierarchy modeling with proper parent-child relationships
  2. Improved context building for policy evaluation that includes teams, roles, and actors
  3. Refactoring of database queries to include more complete information about actors and their roles
  4. Introduction of a PrincipalStore trait that allows for the creation of webs with appropriate authorization checks
  5. Consistent schema between Cedar and the Rust implementation

The code is generally well-structured and follows good practices. I particularly appreciate:

  • The use of iterators for parent-child relationships in ActionName
  • The comprehensive database queries that collect all necessary information for policy evaluation
  • The thoughtful trait designs that maintain backward compatibility while adding new functionality
  • The Cedar schema validation test ensuring consistency between different parts of the system

I do see some areas for potential future optimization around the database access patterns in the context builder, but as mentioned in the PR description, this is intended to be a straightforward initial implementation that can be optimized later.

Overall, this is a solid implementation that achieves the goal of preparing the evaluation of policies when calling from the API layer.

}

pub fn parents(self) -> impl Iterator<Item = Self> {
iter::successors(self.parent(), |&action| action.parent())
Copy link
Member

Choose a reason for hiding this comment

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

Nice implementation of the parent/child relationships for actions. Using iterators for the parents() method is a clean approach. Consider adding a test case that explicitly tests the is_parent_of and is_child_of methods directly, as they're only indirectly tested through the action_ids() test.

@@ -55,6 +55,10 @@ impl ContextBuilder {
self.entities.push(role.to_cedar_entity());
}

pub fn add_team(&mut self, team: &Team) {
Copy link
Member

Choose a reason for hiding this comment

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

The Context implementation nicely centralizes the building of Cedar entities. Consider adding some documentation for the add_team method explaining its purpose, similar to the other methods in this class.

id: UserId::new(ActorEntityUuid::new(row.get(0))),
roles: HashSet::new(),
.map(|row| {
let role_ids = row.get::<_, Option<Vec<Uuid>>>(1).unwrap_or_default();
Copy link
Member

Choose a reason for hiding this comment

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

It's good that you've updated all the query implementations to properly include the roles when fetching actors. One optimization to consider for the future would be to extract this common query pattern (which appears in get_user, get_machine, and get_ai with nearly identical logic) into a shared utility function to reduce code duplication.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's expected that the implementation will diverge and unifying the implementation would just add more work later.

Copy link
Member

Choose a reason for hiding this comment

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

@TimDiekmann I understand your reasoning. If the implementations are expected to diverge, then keeping them separate makes sense to avoid refactoring work later. This approach gives you more flexibility to customize each actor type's query as needed in the future.

///
/// [`PrincipalNotFound`]: PrincipalError::PrincipalNotFound
/// [`StoreError`]: PrincipalError::StoreError
pub async fn build_principal_context(
Copy link
Member

Choose a reason for hiding this comment

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

The implementation of build_principal_context is quite thorough in collecting all the necessary entities for a proper Cedar evaluation. Be careful with the potentially large number of database queries here - this might become a performance bottleneck. Consider adding a comment about future optimization possibilities, such as combining some of these queries or implementing caching strategies.

@@ -195,4 +227,24 @@

Ok(())
}

#[test]
Copy link
Member

Choose a reason for hiding this comment

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

Your implementation of the Cedar schema validation test is valuable to ensure consistency between the Rust code and Cedar schema. However, consider adding a test comment explaining what the test is verifying specifically, as it might not be immediately obvious to other developers.

Copy link
Member

@hashdotai hashdotai left a comment

Choose a reason for hiding this comment

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

This PR introduces a solid foundation for the new policy evaluation system by exposing the Cedar policy interface to the Graph API layer and implementing principal context creation. The changes are well-structured and the implementation is thorough.

The addition of parent-child relationships for actions using iterators is particularly elegant, and the thorough testing of this functionality (including validation against the Cedar schema) gives confidence in the implementation. This will make policy evaluation more flexible and maintainable.

The implementation of the context building functionality is comprehensive, ensuring all necessary entities are available for Cedar policy evaluation. While there are potentially some performance considerations when fetching all context data, this provides a solid starting point that can be optimized in the future.

I've suggested some minor improvements to the documentation and a couple of small cleanups for unused imports. These are relatively minor issues that don't impact the overall quality of the implementation.

///
/// This allows policies associated with the team to be considered during authorization,
/// making the team available as a potential principal in the Cedar evaluation context.
pub fn add_team(&mut self, team: &Team) {
Copy link
Member

Choose a reason for hiding this comment

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

Please add documentation for the add_team method, similar to the style used for the other methods in this class. This would help maintain consistency in the documentation and make it clear what this method is intended for.

Copy link
Member Author

Choose a reason for hiding this comment

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

This literally was added this commit!

@@ -103,14 +142,18 @@
#[expect(clippy::panic_in_result_fn, reason = "Assertions in test are expected")]
mod tests {
use core::error::Error;
use std::collections::{HashMap, HashSet};
Copy link
Member

Choose a reason for hiding this comment

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

This import of HashSet is unused and can be removed.

@@ -9,7 +9,7 @@

use super::{
PolicyValidator,
principal::{Actor, actor::Machine, role::Role},
principal::{Actor, actor::Machine, role::Role, team::Team},
Copy link
Member

Choose a reason for hiding this comment

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

The actor::Machine import is unused after removing the add_machine method and can be safely removed.

/// - Each action defined in the Cedar schema can be mapped to a Rust `ActionName`
/// - The parent-child relationships defined by `action.parents()` match the hierarchical
/// relationships in the Cedar schema (descendants)
#[test]
Copy link
Member

Choose a reason for hiding this comment

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

Please add a more detailed doc comment that explains what this test is specifically verifying. It would be helpful to clarify that it's validating consistency between the Rust ActionName enum hierarchy and the Cedar schema structure.

Copy link
Member Author

Choose a reason for hiding this comment

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

This literally was added this commit!

/// - Combining some queries into a single more complex query
/// - Implementing caching strategies for frequently accessed contexts
/// - Prefetching contexts for related actors in batch operations
pub async fn build_principal_context(
Copy link
Member

Choose a reason for hiding this comment

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

This method performs multiple database queries to build the context, which could become a performance bottleneck for frequent requests. Consider adding a comment about potential future optimizations such as:

  • Combining some queries into a single more complex query
  • Implementing caching strategies for frequently accessed contexts
  • Prefetching contexts for related actors in batch operations

Copy link
Member Author

Choose a reason for hiding this comment

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

This literally was added this commit!

CiaranMn
CiaranMn previously approved these changes Apr 8, 2025
Base automatically changed from t/h-4316-implement-storing-policies-in-the-database to main April 8, 2025 08:05
@TimDiekmann TimDiekmann dismissed CiaranMn’s stale review April 8, 2025 08:05

The base branch was changed.

@CiaranMn CiaranMn requested a review from a team as a code owner April 8, 2025 08:05
@TimDiekmann TimDiekmann force-pushed the t/h-4345-allow-querying-cedar-context-from-database branch from 50f9702 to e0b4dc2 Compare April 8, 2025 09:12
@TimDiekmann TimDiekmann enabled auto-merge April 8, 2025 09:13
@TimDiekmann TimDiekmann requested a review from CiaranMn April 8, 2025 09:13
Copy link
Contributor

github-actions bot commented Apr 8, 2025

Benchmark results

@rust/hash-graph-benches – Integrations

representative_read_entity

Function Value Mean Flame graphs
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2 $$17.0 \mathrm{ms} \pm 195 \mathrm{μs}\left({\color{gray}3.01 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1 $$16.8 \mathrm{ms} \pm 181 \mathrm{μs}\left({\color{gray}0.168 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1 $$16.7 \mathrm{ms} \pm 173 \mathrm{μs}\left({\color{lightgreen}-29.653 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1 $$16.5 \mathrm{ms} \pm 200 \mathrm{μs}\left({\color{gray}-2.273 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1 $$16.8 \mathrm{ms} \pm 175 \mathrm{μs}\left({\color{lightgreen}-28.929 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1 $$16.8 \mathrm{ms} \pm 243 \mathrm{μs}\left({\color{lightgreen}-29.533 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1 $$16.2 \mathrm{ms} \pm 217 \mathrm{μs}\left({\color{gray}-1.782 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1 $$16.8 \mathrm{ms} \pm 224 \mathrm{μs}\left({\color{gray}-0.300 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1 $$16.7 \mathrm{ms} \pm 230 \mathrm{μs}\left({\color{gray}-2.236 \mathrm{\%}}\right) $$ Flame Graph

scaling_read_entity_complete_zero_depth

Function Value Mean Flame graphs
entity_by_id 10 entities $$2.24 \mathrm{ms} \pm 15.2 \mathrm{μs}\left({\color{gray}-0.934 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 5 entities $$2.03 \mathrm{ms} \pm 9.15 \mathrm{μs}\left({\color{gray}-3.032 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 50 entities $$5.33 \mathrm{ms} \pm 24.4 \mathrm{μs}\left({\color{red}27.2 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 25 entities $$2.79 \mathrm{ms} \pm 13.1 \mathrm{μs}\left({\color{gray}-1.656 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1 entities $$2.03 \mathrm{ms} \pm 13.3 \mathrm{μs}\left({\color{gray}-0.217 \mathrm{\%}}\right) $$ Flame Graph

representative_read_entity_type

Function Value Mean Flame graphs
get_entity_type_by_id Account ID: d4e16033-c281-4cde-aa35-9085bf2e7579 $$2.15 \mathrm{ms} \pm 6.74 \mathrm{μs}\left({\color{gray}-1.243 \mathrm{\%}}\right) $$ Flame Graph

scaling_read_entity_linkless

Function Value Mean Flame graphs
entity_by_id 10000 entities $$9.30 \mathrm{ms} \pm 82.0 \mathrm{μs}\left({\color{gray}-1.003 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 100 entities $$2.13 \mathrm{ms} \pm 6.95 \mathrm{μs}\left({\color{lightgreen}-5.735 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 10 entities $$2.03 \mathrm{ms} \pm 8.23 \mathrm{μs}\left({\color{gray}-1.458 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1000 entities $$2.97 \mathrm{ms} \pm 14.9 \mathrm{μs}\left({\color{lightgreen}-7.934 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1 entities $$2.02 \mathrm{ms} \pm 9.07 \mathrm{μs}\left({\color{gray}-1.697 \mathrm{\%}}\right) $$ Flame Graph

representative_read_multiple_entities

Function Value Mean Flame graphs
entity_by_property depths: DT=0, PT=2, ET=2, E=2 $$54.0 \mathrm{ms} \pm 293 \mathrm{μs}\left({\color{gray}-1.170 \mathrm{\%}}\right) $$ Flame Graph
entity_by_property depths: DT=0, PT=0, ET=0, E=0 $$39.3 \mathrm{ms} \pm 338 \mathrm{μs}\left({\color{gray}-0.192 \mathrm{\%}}\right) $$ Flame Graph
entity_by_property depths: DT=0, PT=0, ET=0, E=2 $$43.5 \mathrm{ms} \pm 197 \mathrm{μs}\left({\color{gray}-0.538 \mathrm{\%}}\right) $$ Flame Graph
entity_by_property depths: DT=2, PT=2, ET=2, E=2 $$58.1 \mathrm{ms} \pm 365 \mathrm{μs}\left({\color{gray}0.433 \mathrm{\%}}\right) $$ Flame Graph
entity_by_property depths: DT=255, PT=255, ET=255, E=255 $$66.8 \mathrm{ms} \pm 310 \mathrm{μs}\left({\color{gray}-1.786 \mathrm{\%}}\right) $$ Flame Graph
entity_by_property depths: DT=0, PT=0, ET=2, E=2 $$49.8 \mathrm{ms} \pm 607 \mathrm{μs}\left({\color{gray}0.799 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=0, PT=2, ET=2, E=2 $$88.3 \mathrm{ms} \pm 553 \mathrm{μs}\left({\color{gray}-0.477 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=0, PT=0, ET=0, E=0 $$39.3 \mathrm{ms} \pm 210 \mathrm{μs}\left({\color{gray}-0.025 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=0, PT=0, ET=0, E=2 $$74.4 \mathrm{ms} \pm 386 \mathrm{μs}\left({\color{gray}-1.513 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=2, PT=2, ET=2, E=2 $$92.8 \mathrm{ms} \pm 542 \mathrm{μs}\left({\color{gray}0.371 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=255, PT=255, ET=255, E=255 $$101 \mathrm{ms} \pm 427 \mathrm{μs}\left({\color{gray}-1.102 \mathrm{\%}}\right) $$ Flame Graph
link_by_source_by_property depths: DT=0, PT=0, ET=2, E=2 $$83.7 \mathrm{ms} \pm 417 \mathrm{μs}\left({\color{gray}-1.258 \mathrm{\%}}\right) $$ Flame Graph

scaling_read_entity_complete_one_depth

Function Value Mean Flame graphs
entity_by_id 10 entities $$57.9 \mathrm{ms} \pm 143 \mathrm{μs}\left({\color{gray}-1.678 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 5 entities $$27.1 \mathrm{ms} \pm 116 \mathrm{μs}\left({\color{gray}-1.252 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 50 entities $$5.42 \mathrm{s} \pm 545 \mathrm{ms}\left({\color{gray}-0.479 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 25 entities $$86.0 \mathrm{ms} \pm 569 \mathrm{μs}\left({\color{lightgreen}-53.489 \mathrm{\%}}\right) $$ Flame Graph
entity_by_id 1 entities $$20.9 \mathrm{ms} \pm 104 \mathrm{μs}\left({\color{gray}-2.380 \mathrm{\%}}\right) $$ Flame Graph

@TimDiekmann TimDiekmann added this pull request to the merge queue Apr 8, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Apr 8, 2025
@TimDiekmann TimDiekmann added this pull request to the merge queue Apr 9, 2025
Merged via the queue into main with commit 401ece7 Apr 9, 2025
156 checks passed
@TimDiekmann TimDiekmann deleted the t/h-4345-allow-querying-cedar-context-from-database branch April 9, 2025 07:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/apps > hash* Affects HASH (a `hash-*` app) area/apps > hash-graph area/apps area/deps Relates to third-party dependencies (area) area/libs Relates to first-party libraries/crates/packages (area) area/tests New or updated tests type/eng > backend Owned by the @backend team
Development

Successfully merging this pull request may close these issues.

3 participants