aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src/permissions/check.rs
blob: bb1eae0c8f8610a08ca5e2a56988a292136eb00e (plain) (blame)
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)
    }
}