1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
use eyre::Result;
use crate::{permissions::file::RuleFile, tools::PermissibleToolCall};
pub(crate) struct PermissionRequest<'t> {
call: &'t (dyn PermissibleToolCall + Send + Sync),
}
impl<'t> PermissionRequest<'t> {
pub fn new(call: &'t (dyn PermissibleToolCall + Send + Sync)) -> Self {
Self { call }
}
}
pub(crate) enum PermissionResponse {
Allowed,
Denied,
Ask,
}
pub(crate) struct PermissionChecker {
files: Vec<RuleFile>,
}
impl PermissionChecker {
pub fn new(files: Vec<RuleFile>) -> Self {
Self { files }
}
pub async fn check<'t>(
&self,
request: &'t PermissionRequest<'t>,
) -> Result<PermissionResponse> {
// Files are in order from deepest to shallowest, so we can stop at the first match.
// Within a file, the priority is ask -> deny -> allow
// The first rule type that matches is the one that applies, even if a later rule would contradict it.
for file in &self.files {
for rule in &file.content.permissions.ask {
if request.call.matches_rule(rule) {
tracing::debug!(
"Permission 'ASK' by rule: {} in file: {}",
rule,
file.path.display()
);
return Ok(PermissionResponse::Ask);
}
}
for rule in &file.content.permissions.deny {
if request.call.matches_rule(rule) {
tracing::debug!(
"Permission 'DENY' by rule: {} in file: {}",
rule,
file.path.display()
);
return Ok(PermissionResponse::Denied);
}
}
if request.call.all_covered_by(&file.content.permissions.allow) {
tracing::debug!(
"Permission 'ALLOW' by rules in file: {}",
file.path.display()
);
return Ok(PermissionResponse::Allowed);
}
}
Ok(PermissionResponse::Ask)
}
}
|