fix: preserve nested workdir delegation chains
This commit is contained in:
@@ -69,6 +69,42 @@ impl Drop for WorkdirDelegation {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppliedWorkdirDelegation {
|
||||
pub scoped_session: WorkdirSessionHandle,
|
||||
_leases: Vec<WorkdirDelegation>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AppliedWorkdirDelegation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AppliedWorkdirDelegation")
|
||||
.field("workdir", self.scoped_session.workdir())
|
||||
.field("lease_count", &self._leases.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn apply_delegation_chain(
|
||||
source: WorkdirSessionHandle,
|
||||
requests: impl IntoIterator<Item = WorkdirDelegationRequest>,
|
||||
) -> Result<AppliedWorkdirDelegation, WorkdirError> {
|
||||
let mut current = source;
|
||||
let mut leases = Vec::new();
|
||||
for request in requests {
|
||||
let authority = if current.is_delegation_capable() {
|
||||
current.clone()
|
||||
} else {
|
||||
delegation_capable_session(current.clone())
|
||||
};
|
||||
let lease = authority.delegate(request).await?;
|
||||
current = lease.scoped_session.clone();
|
||||
leases.push(lease);
|
||||
}
|
||||
Ok(AppliedWorkdirDelegation {
|
||||
scoped_session: current,
|
||||
_leases: leases,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SessionValidity {
|
||||
active: AtomicBool,
|
||||
@@ -835,6 +871,38 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn applied_chain_cannot_replace_outer_provider_attenuation() {
|
||||
let root = TempDir::new().unwrap();
|
||||
fs::create_dir_all(root.path().join("outer")).unwrap();
|
||||
fs::create_dir_all(root.path().join("outside")).unwrap();
|
||||
let result = apply_delegation_chain(
|
||||
Arc::new(LocalWorkdirSession::materialized_bound(
|
||||
Workdir::new("delegation-chain-test"),
|
||||
root.path().to_path_buf(),
|
||||
root.path().to_path_buf(),
|
||||
SharedScope::new(
|
||||
Scope::from_config(&ScopeConfig {
|
||||
allow: vec![ScopeRule {
|
||||
target: root.path().to_path_buf(),
|
||||
permission: Permission::Write,
|
||||
recursive: true,
|
||||
}],
|
||||
deny: Vec::new(),
|
||||
})
|
||||
.unwrap(),
|
||||
),
|
||||
WorkdirSessionCapabilities::ALL,
|
||||
)),
|
||||
[
|
||||
request("outer", WorkdirDelegationPermission::Read),
|
||||
request("outside", WorkdirDelegationPermission::Read),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
assert!(matches!(result, Err(WorkdirError::Denied(_))));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn closing_parent_invalidates_delegated_sessions() {
|
||||
let root = TempDir::new().unwrap();
|
||||
|
||||
@@ -72,8 +72,8 @@ pub enum WorkdirSessionOperation {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkdirSessionOperationRequest {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub delegation: Option<crate::WorkdirDelegationRequest>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub delegations: Vec<crate::WorkdirDelegationRequest>,
|
||||
pub operation: WorkdirSessionOperation,
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ mod client {
|
||||
workdir: Workdir,
|
||||
session_id: WorkdirSessionId,
|
||||
capabilities: WorkdirSessionCapabilities,
|
||||
delegation: Option<crate::WorkdirDelegationRequest>,
|
||||
delegations: Vec<crate::WorkdirDelegationRequest>,
|
||||
closed: AtomicBool,
|
||||
}
|
||||
|
||||
@@ -269,7 +269,7 @@ mod client {
|
||||
workdir: Workdir::new(opened.workdir_id.as_str()),
|
||||
session_id: opened.session_id,
|
||||
capabilities: opened.capabilities,
|
||||
delegation: None,
|
||||
delegations: Vec::new(),
|
||||
closed: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
@@ -297,7 +297,7 @@ mod client {
|
||||
],
|
||||
)?;
|
||||
let operation = WorkdirSessionOperationRequest {
|
||||
delegation: self.delegation.clone(),
|
||||
delegations: self.delegations.clone(),
|
||||
operation,
|
||||
};
|
||||
let response = self
|
||||
@@ -335,6 +335,8 @@ mod client {
|
||||
if self.closed.load(Ordering::Acquire) {
|
||||
return Err(WorkdirError::SessionClosed);
|
||||
}
|
||||
let mut delegations = self.delegations.clone();
|
||||
delegations.push(request.clone());
|
||||
Ok(Arc::new(Self {
|
||||
client: self.client.clone(),
|
||||
base_url: self.base_url.clone(),
|
||||
@@ -342,7 +344,7 @@ mod client {
|
||||
workdir: self.workdir.clone(),
|
||||
session_id: self.session_id.clone(),
|
||||
capabilities: self.capabilities,
|
||||
delegation: Some(request.clone()),
|
||||
delegations,
|
||||
closed: AtomicBool::new(false),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -18,8 +18,9 @@ use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use delegation::{
|
||||
ReadOnlyWorkdirSession, WorkdirDelegation, WorkdirDelegationPermission,
|
||||
WorkdirDelegationRequest, WorkdirDelegationRule, delegation_capable_session,
|
||||
AppliedWorkdirDelegation, ReadOnlyWorkdirSession, WorkdirDelegation,
|
||||
WorkdirDelegationPermission, WorkdirDelegationRequest, WorkdirDelegationRule,
|
||||
apply_delegation_chain, delegation_capable_session,
|
||||
};
|
||||
pub use fs_operation::{
|
||||
ContentHash, EditRequest, EditResult, EntryKind, FsPath as WorkdirPath, GlobRequest,
|
||||
|
||||
@@ -320,8 +320,8 @@ mod tests {
|
||||
pub struct WorkspaceWorkdirSessionOperationRequest {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub expected_session_fence: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub delegation: Option<crate::WorkdirDelegationRequest>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub delegations: Vec<crate::WorkdirDelegationRequest>,
|
||||
pub operation: crate::http::WorkdirSessionOperation,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user