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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
//! Delegation path resolution for authentication
//!
//! This module handles the complex logic of resolving delegation paths,
//! including multi-tree traversal and permission clamping.
use std::sync::Arc;
use crate::{
Database, Result,
auth::{
crypto::parse_public_key,
errors::AuthError,
permission::clamp_permission,
settings::AuthSettings,
types::{DelegationStep, PermissionBounds, ResolvedAuth},
},
backend::BackendImpl,
entry::ID,
};
/// Delegation resolver for handling complex delegation paths
pub struct DelegationResolver;
impl DelegationResolver {
/// Create a new delegation resolver
pub fn new() -> Self {
Self
}
/// Resolve delegation path using flat list structure
///
/// This iteratively processes each step in the delegation path,
/// applying permission clamping at each level.
pub fn resolve_delegation_path_with_depth(
&mut self,
steps: &[DelegationStep],
auth_settings: &AuthSettings,
instance: &crate::Instance,
_depth: usize,
) -> Result<ResolvedAuth> {
if steps.is_empty() {
return Err(AuthError::EmptyDelegationPath.into());
}
// Iterate through delegation steps
let mut current_auth_settings = auth_settings.clone();
let current_backend = Arc::clone(instance.backend().as_arc_backend_impl());
let mut cumulative_bounds = None;
// Process all steps except the last one (which should be the final key)
for (i, step) in steps.iter().enumerate() {
let is_final_step = i == steps.len() - 1;
if is_final_step {
// Final step: resolve the actual key
if step.tips.is_some() {
return Err(AuthError::InvalidDelegationStep {
reason: "Final delegation step must not have tips".to_string(),
}
.into());
}
// Resolve the final key directly
let mut resolved = self.resolve_direct_key(&step.key, ¤t_auth_settings)?;
// Apply accumulated permission bounds
if let Some(bounds) = cumulative_bounds {
resolved.effective_permission =
clamp_permission(resolved.effective_permission, &bounds);
}
return Ok(resolved);
} else {
// Intermediate step: load delegated tree
if step.tips.is_none() {
return Err(AuthError::InvalidDelegationStep {
reason: "Non-final delegation step must have tips".to_string(),
}
.into());
}
let tips = step.tips.as_ref().unwrap();
// Get the delegated tree reference
let delegated_tree_ref = current_auth_settings.get_delegated_tree(&step.key)?;
let root_id = delegated_tree_ref.tree.root.clone();
let delegated_tree =
Database::open_readonly(root_id.clone(), instance).map_err(|e| {
AuthError::DelegatedTreeLoadFailed {
tree_id: root_id.to_string(),
source: Box::new(e),
}
})?;
// Validate tips
let current_tips = current_backend.get_tips(&root_id).map_err(|e| {
AuthError::InvalidAuthConfiguration {
reason: format!(
"Failed to get current tips for delegated tree '{root_id}': {e}"
),
}
})?;
let tips_valid =
self.validate_tip_ancestry(tips, ¤t_tips, ¤t_backend)?;
if !tips_valid {
return Err(AuthError::InvalidDelegationTips {
tree_id: root_id.to_string(),
claimed_tips: tips.clone(),
}
.into());
}
// Get delegated tree's auth settings
let delegated_settings = delegated_tree.get_settings().map_err(|e| {
AuthError::InvalidAuthConfiguration {
reason: format!("Failed to get delegated tree settings: {e}"),
}
})?;
current_auth_settings = delegated_settings.get_auth_settings().map_err(|e| {
AuthError::InvalidAuthConfiguration {
reason: format!("Failed to get delegated tree auth settings: {e}"),
}
})?;
// Accumulate permission bounds
if let Some(existing_bounds) = cumulative_bounds {
// Combine bounds by taking the minimum of max permissions
let new_max = std::cmp::min(
existing_bounds.max.clone(),
delegated_tree_ref.permission_bounds.max.clone(),
);
let new_min = match (
existing_bounds.min,
delegated_tree_ref.permission_bounds.min.clone(),
) {
(Some(existing_min), Some(new_min)) => {
Some(std::cmp::max(existing_min, new_min))
}
(Some(existing_min), None) => Some(existing_min),
(None, Some(new_min)) => Some(new_min),
(None, None) => None,
};
cumulative_bounds = Some(PermissionBounds {
max: new_max,
min: new_min,
});
} else {
cumulative_bounds = Some(delegated_tree_ref.permission_bounds);
}
}
}
// This should never be reached due to the final step handling above
Err(AuthError::InvalidDelegationStep {
reason: "Invalid delegation path structure".to_string(),
}
.into())
}
/// Validate tip ancestry using backend's DAG traversal
///
/// This method checks if claimed tips are descendants of or equal to current tips
/// using the backend's DAG traversal capabilities.
///
/// # Arguments
/// * `claimed_tips` - Tips claimed by the entry being validated
/// * `current_tips` - Current tips from the backend
/// * `backend` - Backend to use for DAG traversal
fn validate_tip_ancestry(
&self,
claimed_tips: &[ID],
current_tips: &[ID],
backend: &Arc<dyn BackendImpl>,
) -> Result<bool> {
// Fast path: If no current tips, accept any claimed tips (first entry in tree)
if current_tips.is_empty() {
return Ok(true);
}
// Fast path: If no claimed tips, that's invalid (should have at least some context)
if claimed_tips.is_empty() {
return Ok(false);
}
// Fast path: Check if all claimed tips are identical to current tips
if claimed_tips.len() == current_tips.len()
&& claimed_tips.iter().all(|tip| current_tips.contains(tip))
{
return Ok(true);
}
// Check if each claimed tip is either:
// 1. Equal to a current tip, or
// 2. An ancestor of a current tip (meaning we're using older but valid state)
// 3. A descendant of a current tip (meaning we're ahead of current state)
// Validate each claimed tip
for claimed_tip in claimed_tips {
let mut is_valid = false;
// Fast path: Check if claimed tip equals any current tip
if current_tips.contains(claimed_tip) {
is_valid = true;
} else {
// TODO: For now, we'll use a simplified check and accept the claimed tips
// if they exist in the tree at all. A more sophisticated implementation
// would verify the actual ancestry relationships using the backend's
// DAG traversal methods.
// Try to get the entry to verify it exists in the tree
if backend.get(claimed_tip).is_ok() {
is_valid = true;
}
}
if !is_valid {
return Ok(false);
}
}
Ok(true)
}
/// Resolve a direct key reference from the main tree's auth settings
fn resolve_direct_key(
&self,
key_name: &str,
auth_settings: &AuthSettings,
) -> Result<ResolvedAuth> {
// Get the auth key using AuthSettings
let auth_key = auth_settings.get_key(key_name)?;
let public_key = parse_public_key(auth_key.pubkey())?;
Ok(ResolvedAuth {
public_key,
effective_permission: auth_key.permissions().clone(),
key_status: auth_key.status().clone(),
})
}
}
impl Default for DelegationResolver {
fn default() -> Self {
Self::new()
}
}