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:
@@ -59,9 +59,43 @@ pub async fn basic_auth_middleware(
|
||||
NextcloudAuthError::Unauthorized
|
||||
})?;
|
||||
|
||||
let (username, password) =
|
||||
let (raw_username, password) =
|
||||
parse_basic_auth(auth_header).ok_or(NextcloudAuthError::Unauthorized)?;
|
||||
|
||||
// ── Multi-drive composite-username parse ────────────────────────
|
||||
// POC wire shape: `{username}~{drive_marker}` may appear in the
|
||||
// Basic Auth header. `~` was chosen because it needs no URL
|
||||
// encoding and doesn't collide with UUID hyphens. The marker
|
||||
// after `~` is a chroot SELECTOR (handled by `NcSession` via the
|
||||
// URL `{user}` segment), NOT an auth credential — the password
|
||||
// is verified against the username PREFIX. The middleware just
|
||||
// peels the prefix off so the app-password lookup uses the
|
||||
// canonical name. When no `~` is present, the request is a
|
||||
// plain single-drive ("home") NC sync.
|
||||
//
|
||||
// Reject `name~` (empty marker) and `~marker` (empty username)
|
||||
// at the auth boundary rather than treating them as "missing
|
||||
// marker" — they are unambiguous typos that would otherwise
|
||||
// silently fall into a different code path.
|
||||
let (username, drive_marker): (String, Option<String>) = match raw_username.split_once('~') {
|
||||
Some(("", _)) => {
|
||||
tracing::warn!(
|
||||
"[NC] 401 malformed composite username (empty prefix): {}",
|
||||
raw_username
|
||||
);
|
||||
return Err(NextcloudAuthError::Unauthorized);
|
||||
}
|
||||
Some((_, "")) => {
|
||||
tracing::warn!(
|
||||
"[NC] 401 malformed composite username (empty marker): {}",
|
||||
raw_username
|
||||
);
|
||||
return Err(NextcloudAuthError::Unauthorized);
|
||||
}
|
||||
Some((u, m)) => (u.to_string(), Some(m.to_string())),
|
||||
None => (raw_username.clone(), None),
|
||||
};
|
||||
|
||||
// Check account lockout before attempting password verification (saves CPU).
|
||||
// The lockout is per (account, IP), see #323 for rationale.
|
||||
let client_ip = crate::interfaces::middleware::rate_limit::extract_client_ip(&request);
|
||||
@@ -126,12 +160,68 @@ pub async fn basic_auth_middleware(
|
||||
// making it harder to correlate WebDAV / OCS activity to
|
||||
// a specific principal.
|
||||
tracing::Span::current().record("user_id", user_id.to_string());
|
||||
request.extensions_mut().insert(Arc::new(CurrentUser {
|
||||
let current_user = CurrentUser {
|
||||
id: user_id,
|
||||
username: uname,
|
||||
email,
|
||||
role,
|
||||
}));
|
||||
};
|
||||
|
||||
// ── Resolve chroot from the Basic Auth drive marker ─────
|
||||
// No marker → user's home folder. With a marker →
|
||||
// `get_folder_with_perms` enforces per-folder access (404
|
||||
// anti-enumeration on miss / no-read). Today this is the
|
||||
// sole chroot source; tomorrow it'll come from the
|
||||
// app-password row instead.
|
||||
use crate::application::ports::folder_ports::FolderUseCase;
|
||||
let chroot = match drive_marker.as_deref() {
|
||||
None => {
|
||||
let expected = format!("My Folder - {}", current_user.username);
|
||||
match state
|
||||
.applications
|
||||
.folder_service
|
||||
.list_folders_with_perms(None, current_user.id)
|
||||
.await
|
||||
{
|
||||
Ok(folders) => folders.into_iter().find(|f| f.name == expected),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
Some(folder_id) => state
|
||||
.applications
|
||||
.folder_service
|
||||
.get_folder_with_perms(folder_id, current_user.id)
|
||||
.await
|
||||
.ok(),
|
||||
};
|
||||
if chroot.is_none() {
|
||||
tracing::warn!(
|
||||
"[NC] 404 chroot not resolvable: user={} marker={:?}",
|
||||
current_user.username,
|
||||
drive_marker
|
||||
);
|
||||
return Err(NextcloudAuthError::Unauthorized);
|
||||
}
|
||||
|
||||
request
|
||||
.extensions_mut()
|
||||
.insert(Arc::new(current_user.clone()));
|
||||
request.extensions_mut().insert(Arc::new(
|
||||
crate::interfaces::nextcloud::session::NcSession {
|
||||
user: current_user,
|
||||
raw_username: raw_username.clone(),
|
||||
chroot,
|
||||
},
|
||||
));
|
||||
tracing::Span::current().record(
|
||||
"chroot_id",
|
||||
request
|
||||
.extensions()
|
||||
.get::<Arc<crate::interfaces::nextcloud::session::NcSession>>()
|
||||
.and_then(|s| s.chroot.as_ref())
|
||||
.map(|c| c.id.to_string())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
Ok(next.run(request).await)
|
||||
}
|
||||
Err(_) => {
|
||||
|
||||
Reference in New Issue
Block a user