dl_authorize/
policy.rs

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}