1use crate::effect::Effect;
2use crate::request::Request;
3use crate::statement::Statement;
4use ic_stable_structures::Storable;
5use serde::{Deserialize, Serialize};
6use std::borrow::Cow;
7
8#[derive(Serialize, Deserialize, Default)]
9pub struct Policy {
10 statements: Vec<Statement>,
11}
12
13impl Policy {
14 fn get_effect(&self, request: &Request) -> Effect {
15 let mut effects = vec![];
16 for statement in &self.statements {
17 match statement.get_effect(request) {
18 None => {}
19 Some(effect) => effects.push(effect),
20 }
21 }
22 effects.sort();
23 effects.get(0).cloned().unwrap_or(Effect::Deny)
24 }
25
26 pub fn add_statement(&mut self, statement: Statement) -> () {
27 self.statements.push(statement);
28 }
29}
30
31pub fn get_effect(p: &Policy, r: &Request) -> Effect {
32 return p.get_effect(r);
33}
34
35impl Storable for Policy {
36 fn to_bytes(&self) -> Cow<[u8]> {
37 match serde_json::to_vec(&self) {
38 Ok(result) => {
39 return Cow::from(result.as_slice().to_owned()).to_owned();
40 }
41 Err(_) => {
42 panic!("Failed to serialize!")
43 }
44 }
45 }
46
47 fn from_bytes(bytes: Cow<[u8]>) -> Self {
48 match serde_json::from_slice::<Self>(&*bytes) {
49 Ok(result) => result,
50 Err(_) => {
51 panic!("Failed to deserialize!")
52 }
53 }
54 }
55}
56
57#[cfg(test)]
58mod policy_tests {
59 use crate::effect::Effect;
60 use crate::policy::Policy;
61 use crate::request::RequestResourceBuilder;
62 use crate::statement::{Identity, Statement, StatementIdentity, StatementResource};
63 use candid::Principal;
64
65 use super::*;
66
67 #[test]
68 pub fn it_matches_policy() {
69 let policy = Policy {
70 statements: vec![Statement::new(
71 Effect::Allow,
72 vec![StatementIdentity::Identity(Identity::Principal(
73 Principal::anonymous(),
74 ))],
75 vec!["call".to_string()],
76 vec![StatementResource::Resource("Foo".to_string())],
77 )],
78 };
79
80 assert!(
81 policy.get_effect(&Request::new(
82 "call".to_string(),
83 RequestResourceBuilder::new("Foo").build(),
84 Principal::anonymous()
85 )) == Effect::Allow
86 );
87
88 assert!(
89 policy.get_effect(&Request::new(
90 "call".to_string(),
91 RequestResourceBuilder::new("Bar").build(),
92 Principal::anonymous()
93 )) == Effect::Deny
94 );
95 }
96
97 #[test]
98 pub fn it_selects_least_permissive() {
99 let policy = Policy {
100 statements: vec![
101 Statement::new(
102 Effect::Deny,
103 vec![StatementIdentity::Identity(Identity::Principal(
104 Principal::anonymous(),
105 ))],
106 vec!["call".to_string()],
107 vec![StatementResource::Resource("Foo".to_string())],
108 ),
109 Statement::new(
110 Effect::Allow,
111 vec![StatementIdentity::Identity(Identity::Principal(
112 Principal::anonymous(),
113 ))],
114 vec!["call".to_string()],
115 vec![StatementResource::Resource("Foo".to_string())],
116 ),
117 Statement::new(
118 Effect::Allow,
119 vec![StatementIdentity::Identity(Identity::Principal(
120 Principal::anonymous(),
121 ))],
122 vec!["call".to_string()],
123 vec![StatementResource::Resource("Foo".to_string())
124 .add_nested(StatementResource::Resource("Bar".to_string()))],
125 ),
126 ],
127 };
128
129 assert!(
130 policy.get_effect(&Request::new(
131 "call".to_string(),
132 RequestResourceBuilder::new("Foo").build(),
133 Principal::anonymous()
134 )) == Effect::Deny
135 );
136
137 assert!(
138 policy.get_effect(&Request::new(
139 "call".to_string(),
140 RequestResourceBuilder::new("Foo").add("Bar").build(),
141 Principal::anonymous()
142 )) == Effect::Allow
143 );
144 }
145}