8000 feat(station): validate AllowListed and AllowListedByMetadata rules by olaszakos · Pull Request #570 · dfinity/orbit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(station): validate AllowListed and AllowListedByMetadata rules #570

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions core/station/impl/src/errors/named_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ pub enum NamedRuleError {
#[error("The rule is invalid.")]
InvalidRule { error: String },

// The rule is incompatible with a linked request policy.
#[error("The rule is incompatible with a linked request policy with id {policy_id}.")]
IncompatibleWithLinkedPolicy { policy_id: String, error: String },

// The named rule already exists.
#[error("The named rule already exists.")]
AlreadyExists { name: String },
Expand Down Expand Up @@ -93,6 +97,12 @@ impl DetailableError for NamedRuleError {
details.insert("id".to_string(), id.to_string());
Some(details)
}

NamedRuleError::IncompatibleWithLinkedPolicy { policy_id, error } => {
details.insert("policy_id".to_string(), policy_id.to_string());
details.insert("error".to_string(), error.to_string());
Some(details)
}
}
}
}
17 changes: 17 additions & 0 deletions core/station/impl/src/errors/request_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ pub enum RequestPolicyError {
/// Request policy with id `{id}` already exists.
#[error(r#"Request policy with id `{id}` already exists."#)]
IdAlreadyExists { id: String },
/// The rule `{invalid_rule}` is invalid for the policy with specifier `{specifier}` and rule `{policy_rule}`.
#[error(r#"The rule `{invalid_rule}` is invalid for the policy with specifier `{specifier}` and rule `{policy_rule}`."#)]
InvalidRuleForSpecifier {
invalid_rule: String,
specifier: String,
policy_rule: String,
},
}

impl DetailableError for RequestPolicyError {
Expand All @@ -29,6 +36,16 @@ impl DetailableError for RequestPolicyError {
details.insert("id".to_string(), id.to_string());
Some(details)
}
RequestPolicyError::InvalidRuleForSpecifier {
invalid_rule,
specifier,
policy_rule,
} => {
details.insert("invalid_rule".to_string(), invalid_rule.to_string());
details.insert("specifier".to_string(), specifier.to_string());
details.insert("rule".to_string(), policy_rule.to_string());
Some(details)
}
}
}
}
Expand Down
191 changes: 188 additions & 3 deletions core/station/impl/src/models/named_rule.rs
8000
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ use orbit_essentials::{
storable,
types::UUID,
};
use uuid::Uuid;

use crate::{
core::utils::format_unique_string, errors::NamedRuleError, repositories::NAMED_RULE_REPOSITORY,
core::utils::format_unique_string,
errors::NamedRuleError,
repositories::{NAMED_RULE_REPOSITORY, REQUEST_POLICY_REPOSITORY},
};

use super::{indexes::unique_index::UniqueIndexKey, RequestPolicyRule};
use super::{
indexes::unique_index::UniqueIndexKey, validate_rule_for_specifier, RequestPolicyRule,
};

pub type NamedRuleId = UUID;

Expand Down Expand Up @@ -168,6 +173,25 @@ fn validate_description(description: &Option<String>) -> ModelValidatorResult<Na
Ok(())
}

/// Validates that the named rule is compatible with the policies that reference it.
/// It traverses all policy rules recursively and assumes no circular references.
fn validate_policy_compatibility(
id: &NamedRuleId,
rule: &RequestPolicyRule,
) -> ModelValidatorResult<NamedRuleError> {
let policies = REQUEST_POLICY_REPOSITORY.list();
for policy in policies {
validate_rule_for_specifier(&policy.rule, &policy.specifier, &[(id, rule)]).map_err(
|e| NamedRuleError::IncompatibleWithLinkedPolicy {
policy_id: Uuid::from_bytes(policy.id).hyphenated().to_string(),
error: e.to_string(),
},
)?;
}

Ok(())
}

impl ModelValidator<NamedRuleError> for NamedRule {
fn validate(&self) -> ModelValidatorResult<NamedRuleError> {
validate_name(&self.name)?;
Expand All @@ -181,6 +205,10 @@ impl ModelValidator<NamedRuleError> for NamedRule {

validate_uniqueness(&self.id, &self.name)?;
validate_circular_reference(self)?;

// validate_policy_compatibility assumes no circular references.
validate_policy_compatibility(&self.id, &self.rule)?;

Ok(())
}
}
Expand All @@ -201,8 +229,13 @@ mod test {

use crate::{
errors::NamedRuleError,
models::{NamedRule, RequestPolicyRule},
models::{
request_specifier::RequestSpecifier, AddNamedRuleOperationInput,
AddRequestPolicyOperationInput, EditNamedRuleOperationInput, MetadataItem, NamedRule,
RequestPolicyRule,
},
repositories::NAMED_RULE_REPOSITORY,
services::{NAMED_RULE_SERVICE, REQUEST_POLICY_SERVICE},
};

use super::{validate_description, validate_name};
Expand Down Expand Up @@ -309,4 +342,156 @@ mod test {
Err(NamedRuleError::CircularReference)
));
}

#[test]
fn test_policy_compatibility() {
let named_rule = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test".to_string(),
description: None,
rule: RequestPolicyRule::AutoApproved,
})
.expect("Named rule should be created.");

REQUEST_POLICY_SERVICE
.add_request_policy(AddRequestPolicyOperationInput {
specifier: RequestSpecifier::AddUser,
rule: RequestPolicyRule::NamedRule(named_rule.id),
})
.expect("Policy should be created.");

let named_rule_edit_err = NAMED_RULE_SERVICE
.edit(EditNamedRuleOperationInput {
named_rule_id: named_rule.id,
name: None,
description: None,
rule: Some(RequestPolicyRule::AllowListed),
})
.expect_err("Named rule should be invalid.");

assert_eq!(named_rule_edit_err.code, "INCOMPATIBLE_WITH_LINKED_POLICY");

let named_rule_edit_err = NAMED_RULE_SERVICE
.edit(EditNamedRuleOperationInput {
named_rule_id: named_rule.id,
name: None,
description: None,
rule: Some(RequestPolicyRule::AllowListedByMetadata(MetadataItem {
key: "test".to_string(),
value: "test".to_string(),
})),
})
.expect_err("Named rule should be invalid.");

assert_eq!(named_rule_edit_err.code, "INCOMPATIBLE_WITH_LINKED_POLICY");

NAMED_RULE_SERVICE
.edit(EditNamedRuleOperationInput {
named_rule_id: named_rule.id,
name: None,
description: None,
rule: Some(RequestPolicyRule::AutoApproved),
})
.expect("Named rule should be valid.");
}

#[test]
fn test_indirect_policy_compatibility() {
let named_rule_1 = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_1".to_string(),
description: None,
rule: RequestPolicyRule::AutoApproved,
})
.expect("Named rule should be created.");

let named_rule_2 = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_2".to_string(),
description: None,
rule: RequestPolicyRule::NamedRule(named_rule_1.id),
})
.expect("Named rule should be created.");

REQUEST_POLICY_SERVICE
.add_request_policy(AddRequestPolicyOperationInput {
specifier: RequestSpecifier::AddUser,
rule: RequestPolicyRule::NamedRule(named_rule_2.id),
})
.expect("Policy should be created.");

let named_rule_edit_err = NAMED_RULE_SERVICE
.edit(EditNamedRuleOperationInput {
named_rule_id: named_rule_1.id,
name: None,
description: None,
rule: Some(RequestPolicyRule::AllowListed),
})
.expect_err("Named rule should be invalid.");

assert_eq!(named_rule_edit_err.code, "INCOMPATIBLE_WITH_LINKED_POLICY");
}

#[test]
fn test_indirect_policy_compatibility_root_change() {
let named_rule_1 = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_1".to_string(),
description: None,
rule: RequestPolicyRule::AutoApproved,
})
.expect("Named rule should be created.");

let named_rule_2 = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_2".to_string(),
description: None,
rule: RequestPolicyRule::NamedRule(named_rule_1.id),
})
.expect("Named rule should be created.");

let named_rule_3 = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_3".to_string(),
description: None,
rule: RequestPolicyRule::AllowListed,
})
.expect("Named rule should be created.");

REQUEST_POLICY_SERVICE
.add_request_policy(AddRequestPolicyOperationInput {
specifier: RequestSpecifier::AddUser,
rule: RequestPolicyRule::NamedRule(named_rule_2.id),
})
.expect("Policy should be created.");

let named_rule_edit_err = NAMED_RULE_SERVICE
.edit(EditNamedRuleOperationInput {
named_rule_id: named_rule_2.id,
name: None,
description: None,
rule: Some(RequestPolicyRule::NamedRule(named_rule_3.id)),
})
.expect_err("Named rule should be invalid.");

assert_eq!(named_rule_edit_err.code, "INCOMPATIBLE_WITH_LINKED_POLICY");
}

#[test]
fn test_nested_rule_compatibility() {
let named_rule = NAMED_RULE_SERVICE
.create(AddNamedRuleOperationInput {
name: "test_1".to_string(),
description: None,
rule: RequestPolicyRule::And(vec![RequestPolicyRule::AllowListed]),
})
.expect("Named rule should be created.");

REQUEST_POLICY_SERVICE
.add_request_policy(AddRequestPolicyOperationInput {
specifier: RequestSpecifier::AddUser,
rule: RequestPolicyRule::NamedRule(named_rule.id),
})
.expect_err("Policy should be incompatible with the named rule.");
}
}
Loading
Loading
0