POC(nextcloud): add chroot on nextcloud
Bring chroot to nextcloud capability: login on Nextcloud via username="{user}~{folder-uuid}
Doing a such login will chroot the folder folder-uuid
If user has several folder as root (parent=None), the login flow will request which
folder user want to chroot
This commit is contained in:
@@ -3,6 +3,7 @@ use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use rand_core::RngCore;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Maximum number of concurrent pending login flows to prevent memory exhaustion.
|
||||
const MAX_PENDING_FLOWS: usize = 1000;
|
||||
@@ -30,6 +31,13 @@ pub struct LoginResult {
|
||||
struct PendingFlow {
|
||||
created_at: Instant,
|
||||
poll_token: String,
|
||||
/// Set after the user authenticates on the login page **and** has more
|
||||
/// than one root drive — the flow is paused until the user picks a
|
||||
/// drive on the picker page. Consumed by `take_pending_user` when the
|
||||
/// picker submission arrives, so the second step is single-use even
|
||||
/// if the flow token leaks. `None` for single-drive accounts (legacy
|
||||
/// path goes straight to `completed`).
|
||||
pending_user_id: Option<Uuid>,
|
||||
completed: Option<LoginResult>,
|
||||
}
|
||||
|
||||
@@ -80,6 +88,7 @@ impl NextcloudLoginFlowService {
|
||||
PendingFlow {
|
||||
created_at: Instant::now(),
|
||||
poll_token: poll_token.clone(),
|
||||
pending_user_id: None,
|
||||
completed: None,
|
||||
},
|
||||
);
|
||||
@@ -101,6 +110,39 @@ impl NextcloudLoginFlowService {
|
||||
state.flows.contains_key(flow_token)
|
||||
}
|
||||
|
||||
/// Stash a verified user_id on the flow so a follow-up drive-pick
|
||||
/// request can prove "this browser just authenticated" without
|
||||
/// asking for the password again. Returns `false` if the flow
|
||||
/// token is unknown or expired.
|
||||
///
|
||||
/// Only used on multi-drive accounts — single-drive logins go
|
||||
/// straight to [`complete`](Self::complete).
|
||||
pub fn mark_awaiting_drive(&self, flow_token: &str, user_id: Uuid) -> bool {
|
||||
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
|
||||
prune_expired(&mut state, self.ttl);
|
||||
match state.flows.get_mut(flow_token) {
|
||||
Some(pending) => {
|
||||
pending.pending_user_id = Some(user_id);
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the stashed user_id (single-use). Returns the user_id
|
||||
/// when the flow is in "awaiting drive choice" state, or `None`
|
||||
/// when the flow is unknown, expired, or was never marked. Single-
|
||||
/// use semantics make this safe even if the flow token leaks: the
|
||||
/// second drive-pick attempt finds nothing to consume.
|
||||
pub fn take_pending_user(&self, flow_token: &str) -> Option<Uuid> {
|
||||
let mut state = self.state.lock().unwrap_or_else(|e| e.into_inner());
|
||||
prune_expired(&mut state, self.ttl);
|
||||
state
|
||||
.flows
|
||||
.get_mut(flow_token)
|
||||
.and_then(|pending| pending.pending_user_id.take())
|
||||
}
|
||||
|
||||
pub fn complete(
|
||||
&self,
|
||||
flow_token: &str,
|
||||
@@ -257,6 +299,35 @@ mod tests {
|
||||
assert!(svc.poll(&info.poll_token).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_awaiting_drive_then_take_pending_user() {
|
||||
let svc = service();
|
||||
let info = svc.initiate("https://cloud.example.com").unwrap();
|
||||
let flow_token = info.login_url.rsplit('/').next().unwrap();
|
||||
let uid = Uuid::new_v4();
|
||||
|
||||
assert!(svc.mark_awaiting_drive(flow_token, uid));
|
||||
// First take consumes the slot.
|
||||
assert_eq!(svc.take_pending_user(flow_token), Some(uid));
|
||||
// Second take must return None (single-use).
|
||||
assert_eq!(svc.take_pending_user(flow_token), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mark_awaiting_drive_unknown_flow_returns_false() {
|
||||
let svc = service();
|
||||
assert!(!svc.mark_awaiting_drive("nonexistent", Uuid::new_v4()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_take_pending_user_without_mark_returns_none() {
|
||||
let svc = service();
|
||||
let info = svc.initiate("https://cloud.example.com").unwrap();
|
||||
let flow_token = info.login_url.rsplit('/').next().unwrap();
|
||||
// Flow exists but mark_awaiting_drive was never called.
|
||||
assert_eq!(svc.take_pending_user(flow_token), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_pending_flows_cap() {
|
||||
let svc = NextcloudLoginFlowService::new(Duration::from_secs(600));
|
||||
|
||||
Reference in New Issue
Block a user