mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(media): use full community host label
Replace the community alias label with the full server-resolved tenant hostname in S3 metadata and sidecars. Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
@@ -22,8 +22,8 @@ pub const BUZZ_UPLOADER_ID_META_KEY: &str = "buzz-uploader-id";
|
||||
pub const BUZZ_UPLOADER_NAME_META_KEY: &str = "buzz-uploader-name";
|
||||
/// Bare S3 user-metadata key for the server-resolved community id.
|
||||
pub const BUZZ_COMMUNITY_ID_META_KEY: &str = "buzz-community-id";
|
||||
/// Bare S3 user-metadata key for the human-readable community host prefix.
|
||||
pub const BUZZ_COMMUNITY_ALIAS_META_KEY: &str = "buzz-community-alias";
|
||||
/// Bare S3 user-metadata key for the server-resolved community host.
|
||||
pub const BUZZ_COMMUNITY_HOST_META_KEY: &str = "buzz-community-host";
|
||||
|
||||
/// S3-compatible object storage client.
|
||||
pub struct MediaStorage {
|
||||
@@ -407,7 +407,7 @@ mod tests {
|
||||
(BUZZ_UPLOADER_ID_META_KEY, "aabbcc"),
|
||||
(BUZZ_UPLOADER_NAME_META_KEY, "Ada"),
|
||||
(BUZZ_COMMUNITY_ID_META_KEY, "0000-1111"),
|
||||
(BUZZ_COMMUNITY_ALIAS_META_KEY, "moderation"),
|
||||
(BUZZ_COMMUNITY_HOST_META_KEY, "moderation.buzz.example"),
|
||||
])
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -430,9 +430,9 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(format!("x-amz-meta-{BUZZ_COMMUNITY_ALIAS_META_KEY}"))
|
||||
.get(format!("x-amz-meta-{BUZZ_COMMUNITY_HOST_META_KEY}"))
|
||||
.unwrap(),
|
||||
"moderation"
|
||||
"moderation.buzz.example"
|
||||
);
|
||||
|
||||
// Control characters in values are rejected, not silently mangled.
|
||||
@@ -451,8 +451,8 @@ mod tests {
|
||||
"0000-1111".to_string(),
|
||||
);
|
||||
metadata.insert(
|
||||
BUZZ_COMMUNITY_ALIAS_META_KEY.to_string(),
|
||||
"moderation".to_string(),
|
||||
BUZZ_COMMUNITY_HOST_META_KEY.to_string(),
|
||||
"moderation.buzz.example".to_string(),
|
||||
);
|
||||
|
||||
let result = s3::serde_types::HeadObjectResult {
|
||||
@@ -476,8 +476,8 @@ mod tests {
|
||||
Some(&"0000-1111".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
head.metadata.get(BUZZ_COMMUNITY_ALIAS_META_KEY),
|
||||
Some(&"moderation".to_string())
|
||||
head.metadata.get(BUZZ_COMMUNITY_HOST_META_KEY),
|
||||
Some(&"moderation.buzz.example".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -491,28 +491,28 @@ mod tests {
|
||||
assert_eq!(meta.uploader_id, None);
|
||||
assert_eq!(meta.uploader_name, None);
|
||||
assert_eq!(meta.community_id, None);
|
||||
assert_eq!(meta.community_alias, None);
|
||||
assert_eq!(meta.community_host, None);
|
||||
|
||||
// Absent attribution is omitted from serialized output (not null).
|
||||
let json = serde_json::to_value(&meta).unwrap();
|
||||
assert!(json.get("uploader_id").is_none());
|
||||
assert!(json.get("uploader_name").is_none());
|
||||
assert!(json.get("community_id").is_none());
|
||||
assert!(json.get("community_alias").is_none());
|
||||
assert!(json.get("community_host").is_none());
|
||||
|
||||
// Populated attribution round-trips.
|
||||
let meta = BlobMeta {
|
||||
uploader_id: Some("aa".repeat(32)),
|
||||
uploader_name: Some("Ada".to_string()),
|
||||
community_id: Some("6b8e1c2a-0000-0000-0000-000000000000".to_string()),
|
||||
community_alias: Some("moderation".to_string()),
|
||||
community_host: Some("moderation.buzz.example".to_string()),
|
||||
..meta
|
||||
};
|
||||
let round: BlobMeta = serde_json::from_str(&serde_json::to_string(&meta).unwrap()).unwrap();
|
||||
assert_eq!(round.uploader_id, meta.uploader_id);
|
||||
assert_eq!(round.uploader_name, meta.uploader_name);
|
||||
assert_eq!(round.community_id, meta.community_id);
|
||||
assert_eq!(round.community_alias, meta.community_alias);
|
||||
assert_eq!(round.community_host, meta.community_host);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,9 +588,8 @@ pub struct BlobMeta {
|
||||
/// out of its keyed location; `None` on pre-attribution sidecars.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub community_id: Option<String>,
|
||||
/// Human-readable community alias derived from the server-resolved host's
|
||||
/// first label (for example `team` from `team.example.com`). Readability
|
||||
/// hint only; `community_id` remains authoritative.
|
||||
/// Server-resolved community host (for example `team.example.com`).
|
||||
/// Readability hint only; `community_id` remains authoritative.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub community_alias: Option<String>,
|
||||
pub community_host: Option<String>,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::auth::verify_blossom_upload_auth;
|
||||
use crate::config::MediaConfig;
|
||||
use crate::error::MediaError;
|
||||
use crate::storage::{
|
||||
BlobMeta, MediaStorage, BUZZ_COMMUNITY_ALIAS_META_KEY, BUZZ_COMMUNITY_ID_META_KEY,
|
||||
BlobMeta, MediaStorage, BUZZ_COMMUNITY_HOST_META_KEY, BUZZ_COMMUNITY_ID_META_KEY,
|
||||
BUZZ_UPLOADER_ID_META_KEY, BUZZ_UPLOADER_NAME_META_KEY,
|
||||
};
|
||||
use crate::thumbnail::generate_image_metadata_sync;
|
||||
@@ -19,13 +19,13 @@ use crate::validation::{
|
||||
};
|
||||
|
||||
/// Readability metadata for upload attribution. The id fields remain
|
||||
/// authoritative; names/aliases are best-effort labels for moderators.
|
||||
/// authoritative; names/hosts are best-effort labels for moderators.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct UploadAttributionLabels {
|
||||
/// Configured display name for the authenticated uploader, if known.
|
||||
pub uploader_name: Option<String>,
|
||||
/// Human-readable alias derived from the server-resolved tenant host.
|
||||
pub community_alias: Option<String>,
|
||||
/// Server-resolved tenant host for the community.
|
||||
pub community_host: Option<String>,
|
||||
}
|
||||
|
||||
impl UploadAttributionLabels {
|
||||
@@ -33,7 +33,7 @@ impl UploadAttributionLabels {
|
||||
pub fn from_profile_and_host(uploader_name: Option<String>, tenant_host: &str) -> Self {
|
||||
Self {
|
||||
uploader_name: uploader_name.and_then(sanitize_label),
|
||||
community_alias: community_alias_from_host(tenant_host),
|
||||
community_host: community_host_from_host(tenant_host),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ fn attribution_meta<'a>(
|
||||
if let Some(uploader_name) = labels.uploader_name.as_deref() {
|
||||
metadata.push((BUZZ_UPLOADER_NAME_META_KEY, uploader_name));
|
||||
}
|
||||
if let Some(community_alias) = labels.community_alias.as_deref() {
|
||||
metadata.push((BUZZ_COMMUNITY_ALIAS_META_KEY, community_alias));
|
||||
if let Some(community_host) = labels.community_host.as_deref() {
|
||||
metadata.push((BUZZ_COMMUNITY_HOST_META_KEY, community_host));
|
||||
}
|
||||
metadata
|
||||
}
|
||||
@@ -82,10 +82,8 @@ fn sanitize_label(label: String) -> Option<String> {
|
||||
(!out.is_empty()).then_some(out)
|
||||
}
|
||||
|
||||
fn community_alias_from_host(host: &str) -> Option<String> {
|
||||
let authority = host.split(':').next().unwrap_or(host).trim();
|
||||
let alias = authority.split('.').next().unwrap_or(authority);
|
||||
sanitize_label(alias.to_string())
|
||||
fn community_host_from_host(host: &str) -> Option<String> {
|
||||
sanitize_label(host.to_string())
|
||||
}
|
||||
|
||||
/// Shared buffered-upload pipeline for the image and generic-file paths.
|
||||
@@ -307,7 +305,7 @@ pub async fn process_file_upload(
|
||||
uploader_id: Some(input.uploader_id),
|
||||
uploader_name: input.labels.uploader_name,
|
||||
community_id: Some(input.community_id),
|
||||
community_alias: input.labels.community_alias,
|
||||
community_host: input.labels.community_host,
|
||||
};
|
||||
storage.put_sidecar(ctx, &input.sha256, &meta).await?;
|
||||
Ok(meta)
|
||||
@@ -514,7 +512,7 @@ pub async fn process_video_upload(
|
||||
uploader_id: Some(uploader_id),
|
||||
uploader_name: labels.uploader_name,
|
||||
community_id: Some(community_id),
|
||||
community_alias: labels.community_alias,
|
||||
community_host: labels.community_host,
|
||||
};
|
||||
storage.put_sidecar(ctx, &sha256_hex, &meta).await?;
|
||||
|
||||
@@ -552,7 +550,7 @@ async fn generate_and_store_metadata(
|
||||
meta.uploader_id = Some(input.uploader_id);
|
||||
meta.uploader_name = input.labels.uploader_name;
|
||||
meta.community_id = Some(input.community_id);
|
||||
meta.community_alias = input.labels.community_alias;
|
||||
meta.community_host = input.labels.community_host;
|
||||
|
||||
if let Some(ref tb) = thumb_bytes {
|
||||
// The thumbnail is a derived object with its own S3 key, so it carries
|
||||
@@ -568,7 +566,7 @@ async fn generate_and_store_metadata(
|
||||
meta.community_id.as_deref().unwrap_or_default(),
|
||||
&UploadAttributionLabels {
|
||||
uploader_name: meta.uploader_name.clone(),
|
||||
community_alias: meta.community_alias.clone(),
|
||||
community_host: meta.community_host.clone(),
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -638,7 +636,7 @@ mod tests {
|
||||
uploader_id: None,
|
||||
uploader_name: None,
|
||||
community_id: None,
|
||||
community_alias: None,
|
||||
community_host: None,
|
||||
};
|
||||
|
||||
let desc = build_descriptor(
|
||||
@@ -700,7 +698,7 @@ mod tests {
|
||||
uploader_id: None,
|
||||
uploader_name: None,
|
||||
community_id: None,
|
||||
community_alias: None,
|
||||
community_host: None,
|
||||
};
|
||||
|
||||
let desc = build_descriptor(
|
||||
@@ -759,18 +757,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_attribution_labels_are_sanitized_and_host_aliased() {
|
||||
fn upload_attribution_labels_are_sanitized_and_use_full_host() {
|
||||
let labels = UploadAttributionLabels::from_profile_and_host(
|
||||
Some(" Ada Lovelace\n🚀 ".to_string()),
|
||||
"moderation.buzz.example",
|
||||
);
|
||||
|
||||
assert_eq!(labels.uploader_name.as_deref(), Some("Ada Lovelace"));
|
||||
assert_eq!(labels.community_alias.as_deref(), Some("moderation"));
|
||||
assert_eq!(
|
||||
labels.community_host.as_deref(),
|
||||
Some("moderation.buzz.example")
|
||||
);
|
||||
|
||||
let localhost = UploadAttributionLabels::from_profile_and_host(None, "localhost:3000");
|
||||
assert_eq!(localhost.uploader_name, None);
|
||||
assert_eq!(localhost.community_alias.as_deref(), Some("localhost"));
|
||||
assert_eq!(localhost.community_host.as_deref(), Some("localhost:3000"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
use buzz_media::config::MediaConfig;
|
||||
use buzz_media::storage::{
|
||||
MediaStorage, BUZZ_COMMUNITY_ALIAS_META_KEY, BUZZ_COMMUNITY_ID_META_KEY,
|
||||
MediaStorage, BUZZ_COMMUNITY_HOST_META_KEY, BUZZ_COMMUNITY_ID_META_KEY,
|
||||
BUZZ_UPLOADER_ID_META_KEY, BUZZ_UPLOADER_NAME_META_KEY,
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ async fn static_creds_round_trip_against_minio() {
|
||||
(BUZZ_UPLOADER_ID_META_KEY, "test-uploader"),
|
||||
(BUZZ_UPLOADER_NAME_META_KEY, "Test Uploader"),
|
||||
(BUZZ_COMMUNITY_ID_META_KEY, "test-community"),
|
||||
(BUZZ_COMMUNITY_ALIAS_META_KEY, "moderation"),
|
||||
(BUZZ_COMMUNITY_HOST_META_KEY, "moderation.buzz.example"),
|
||||
],
|
||||
)
|
||||
.await
|
||||
@@ -87,8 +87,8 @@ async fn static_creds_round_trip_against_minio() {
|
||||
Some(&"test-community".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
meta.metadata.get(BUZZ_COMMUNITY_ALIAS_META_KEY),
|
||||
Some(&"moderation".to_string())
|
||||
meta.metadata.get(BUZZ_COMMUNITY_HOST_META_KEY),
|
||||
Some(&"moderation.buzz.example".to_string())
|
||||
);
|
||||
|
||||
// GET round-trips the bytes
|
||||
|
||||
Reference in New Issue
Block a user