返回 CodeWhale
auth.rs
根目录 / crates / memory / src / auth.rs
1 use crate::{Error, Result, Scope};
2 use std::collections::BTreeSet;
3
4 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5 pub enum Capability {
6 Read,
7 Propose,
8 Review,
9 Correct,
10 Forget,
11 Index,
12 Link,
13 Checkpoint,
14 Export,
15 Maintenance,
16 ContextDispatch,
17 }
18
19 /// Constructed by trusted host code. Deliberately NOT Deserialize: request bodies
20 /// cannot create or enlarge grants. These checks do not sandbox a hostile local OS user.
21 #[derive(Debug, Clone)]
22 pub struct Access {
23 actor: String,
24 reads: BTreeSet<Scope>,
25 writes: BTreeSet<Scope>,
26 capabilities: BTreeSet<Capability>,
27 }
28 impl Access {
29 pub fn new(
30 actor: impl Into<String>,
31 reads: Vec<Scope>,
32 writes: Vec<Scope>,
33 caps: Vec<Capability>,
34 ) -> Result<Self> {
35 let actor = actor.into();
36 if actor.trim().is_empty() || actor.len() > 256 {
37 return Err(Error::Invalid("actor identity is required".into()));
38 }
39 if reads.len() > 64 || writes.len() > 64 {
40 return Err(Error::Invalid(
41 "at most 64 exact scopes may be granted".into(),
42 ));
43 }
44 for scope in reads.iter().chain(writes.iter()) {
45 scope.validate()?;
46 }
47 if let Some(first) = reads.first()
48 && reads
49 .iter()
50 .chain(writes.iter())
51 .any(|s| s.tenant != first.tenant || s.user != first.user)
52 {
53 return Err(Error::Invalid(
54 "one access context must belong to exactly one tenant and user".into(),
55 ));
56 }
57 // One trusted freshness snapshot represents one live workspace/branch/
58 // session/agent. Do not apply its file hashes to another repository.
59 for field in 0..4 {
60 let values: BTreeSet<&str> = reads
61 .iter()
62 .filter_map(|s| match field {
63 0 => s.workspace.as_deref(),
64 1 => s.branch.as_deref(),
65 2 => s.session.as_deref(),
66 _ => s.agent.as_deref(),
67 })
68 .collect();
69 if values.len() > 1 {
70 return Err(Error::Invalid("one access context cannot span different workspace, branch, session, or agent identities".into()));
71 }
72 }
73 let reads: BTreeSet<_> = reads.into_iter().collect();
74 let writes: BTreeSet<_> = writes.into_iter().collect();
75 if !writes.is_subset(&reads) {
76 return Err(Error::Denied);
77 }
78 Ok(Self {
79 actor,
80 reads,
81 writes,
82 capabilities: caps.into_iter().collect(),
83 })
84 }
85 pub fn operator(scopes: Vec<Scope>) -> Result<Self> {
86 Self::new(
87 "local-operator",
88 scopes.clone(),
89 scopes,
90 vec![
91 Capability::Read,
92 Capability::Propose,
93 Capability::Review,
94 Capability::Correct,
95 Capability::Forget,
96 Capability::Index,
97 Capability::Link,
98 Capability::Checkpoint,
99 Capability::Export,
100 Capability::Maintenance,
101 Capability::ContextDispatch,
102 ],
103 )
104 }
105 pub fn agent(scopes: Vec<Scope>) -> Result<Self> {
106 Self::new(
107 "agent",
108 scopes.clone(),
109 scopes,
110 vec![
111 Capability::Read,
112 Capability::Propose,
113 Capability::Link,
114 Capability::Checkpoint,
115 ],
116 )
117 }
118 pub fn readonly(scopes: Vec<Scope>) -> Result<Self> {
119 Self::new("reader", scopes, vec![], vec![Capability::Read])
120 }
121 pub fn delegate(
122 &self,
123 actor: impl Into<String>,
124 reads: Vec<Scope>,
125 writes: Vec<Scope>,
126 caps: Vec<Capability>,
127 ) -> Result<Self> {
128 let child = Self::new(actor, reads, writes, caps)?;
129 if !child.reads.is_subset(&self.reads)
130 || !child.writes.is_subset(&self.writes)
131 || !child.capabilities.is_subset(&self.capabilities)
132 {
133 return Err(Error::Denied);
134 }
135 Ok(child)
136 }
137 pub fn has(&self, cap: Capability) -> bool {
138 self.capabilities.contains(&cap)
139 }
140 pub fn require(&self, cap: Capability) -> Result<()> {
141 if self.has(cap) {
142 Ok(())
143 } else {
144 Err(Error::Denied)
145 }
146 }
147 pub fn read(&self, scope: &Scope) -> Result<()> {
148 self.require(Capability::Read)?;
149 if self.reads.contains(scope) {
150 Ok(())
151 } else {
152 Err(Error::NotFound)
153 }
154 }
155 pub fn write(&self, scope: &Scope, cap: Capability) -> Result<()> {
156 self.require(cap)?;
157 if self.writes.contains(scope) {
158 Ok(())
159 } else {
160 Err(Error::Denied)
161 }
162 }
163 pub fn scopes(&self) -> impl Iterator<Item = &Scope> {
164 self.reads.iter()
165 }
166 pub fn writable_scopes(&self) -> impl Iterator<Item = &Scope> {
167 self.writes.iter()
168 }
169 pub(crate) fn scope_json(&self) -> Result<String> {
170 Ok(serde_json::to_string(
171 &self
172 .reads
173 .iter()
174 .map(Scope::key)
175 .collect::<Result<Vec<_>>>()?,
176 )?)
177 }
178 pub(crate) fn actor_hash(&self) -> String {
179 crate::policy::sha256(self.actor.as_bytes())
180 }
181 }
182
182 lines RUST