Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68e21f4bef |
@@ -116,19 +116,35 @@ impl ShareBrowseService {
|
||||
self.list_inner(folder_id, resolved.owner_id).await
|
||||
}
|
||||
|
||||
/// AuthZ gate for `/api/s/{token}/file/{file_id}`: the requested file
|
||||
/// must either BE the shared item (single-file share — the public landing
|
||||
/// page's inline media preview streams through here) or live inside the
|
||||
/// shared folder's subtree (folder share). Anything else is NotFound —
|
||||
/// the same shape as "file doesn't exist", so the endpoint can't be used
|
||||
/// to enumerate file ids.
|
||||
pub async fn assert_file_in_share(
|
||||
&self,
|
||||
token: &str,
|
||||
file_id: &str,
|
||||
unlock_jwt: Option<&str>,
|
||||
) -> Result<(), DomainError> {
|
||||
let resolved = self.resolve_folder_share(token, unlock_jwt).await?;
|
||||
let share = self
|
||||
.share_service
|
||||
.get_shared_link_with_unlock(token, unlock_jwt)
|
||||
.await?;
|
||||
|
||||
if !self
|
||||
.folder_repo
|
||||
.is_file_in_subtree(file_id, &resolved.root_folder_id)
|
||||
.await?
|
||||
{
|
||||
let in_scope = match share.item_type.as_str() {
|
||||
// Single-file share: only the shared item itself may be streamed.
|
||||
"file" => file_id == share.item_id,
|
||||
// Folder share: the file must live in the shared subtree.
|
||||
"folder" => {
|
||||
self.folder_repo
|
||||
.is_file_in_subtree(file_id, &share.item_id)
|
||||
.await?
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if !in_scope {
|
||||
return Err(DomainError::not_found("File", file_id));
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -639,7 +639,7 @@ pub async fn list_share_contents_subfolder(
|
||||
path = "/api/s/{token}/file/{file_id}",
|
||||
params(
|
||||
("token" = String, Path, description = "Share token"),
|
||||
("file_id" = String, Path, description = "File ID (must be inside the share)")
|
||||
("file_id" = String, Path, description = "File ID (the shared item itself, or a file inside the shared folder's subtree)")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "File content (or 206 for Range request)"),
|
||||
|
||||
@@ -197,6 +197,24 @@ HTTP 200
|
||||
jsonpath "$.item_type" == "file"
|
||||
|
||||
|
||||
# The public landing page's inline media preview streams the shared file
|
||||
# through /api/s/{token}/file/{file_id} — the requested file IS the shared
|
||||
# item here, so the AuthZ gate must accept it (Range-aware 200, inline
|
||||
# disposition so <video>/<img> can render it).
|
||||
GET {{base_url}}/api/s/{{file_share_token}}/file/{{shared_file_id}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
header "Content-Disposition" contains "hello.txt"
|
||||
|
||||
|
||||
# A file id that is NOT the shared item must 404 on a file-share token —
|
||||
# same anti-enumeration shape as the folder-share probe above.
|
||||
GET {{base_url}}/api/s/{{file_share_token}}/file/{{outsider_file_id}}
|
||||
|
||||
HTTP 404
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# 9 — Mint a password-protected share on the same folder.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user