修复公开资产授权一致性与签名期限
公开资产 metadata 和作品授权改为在同一 SpacetimeDB 事务快照中判断 移除连接级公开授权订阅缓存对 ACL 的参与 公开读取签名有效期上限固定为 600 秒并保留 owner 和 admin 行为 同步生成 bindings 并更新后端契约与项目记忆
This commit is contained in:
@@ -50,6 +50,7 @@ const SUPPORTED_ASSET_HISTORY_KINDS: [&str; 9] = [
|
||||
// 中文注释:同源字节读取同时服务图片转 Data URL 与 Match3D 私有 GLB 预览,Rodin GLB 可能明显超过图片上限。
|
||||
const ASSET_READ_BYTES_MAX_SIZE_BYTES: u64 = 120 * 1024 * 1024;
|
||||
const ASSET_READ_BYTES_DEFAULT_EXPIRE_SECONDS: u64 = 300;
|
||||
const PUBLIC_ASSET_READ_MAX_EXPIRE_SECONDS: u64 = 600;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum AssetReadAuthorization {
|
||||
@@ -58,6 +59,12 @@ pub(crate) enum AssetReadAuthorization {
|
||||
Admin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum AssetReadAccessScope {
|
||||
Public,
|
||||
Privileged,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
struct AssetReadTarget {
|
||||
object_key: String,
|
||||
@@ -188,12 +195,17 @@ pub(crate) async fn get_asset_read_url_with_query(
|
||||
})?;
|
||||
|
||||
let target = resolve_asset_read_target(&query)?;
|
||||
authorize_asset_read_target(state, oss_client.config_bucket(), &target, &authorization).await?;
|
||||
let access_scope =
|
||||
authorize_asset_read_target(state, oss_client.config_bucket(), &target, &authorization)
|
||||
.await?;
|
||||
|
||||
let signed = oss_client
|
||||
.sign_get_object_url(OssSignedGetObjectUrlRequest {
|
||||
object_key: target.object_key,
|
||||
expire_seconds: query.expire_seconds,
|
||||
expire_seconds: clamp_public_asset_read_expire_seconds(
|
||||
query.expire_seconds,
|
||||
access_scope,
|
||||
),
|
||||
})
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
@@ -298,16 +310,20 @@ pub async fn get_asset_read_bytes(
|
||||
"/api/assets/read-bytes",
|
||||
)
|
||||
.await?;
|
||||
authorize_asset_read_target(&state, oss_client.config_bucket(), &target, &authorization)
|
||||
.await?;
|
||||
let access_scope =
|
||||
authorize_asset_read_target(&state, oss_client.config_bucket(), &target, &authorization)
|
||||
.await?;
|
||||
|
||||
let signed = oss_client
|
||||
.sign_get_object_url(OssSignedGetObjectUrlRequest {
|
||||
object_key: target.object_key,
|
||||
expire_seconds: Some(
|
||||
query
|
||||
.expire_seconds
|
||||
.unwrap_or(ASSET_READ_BYTES_DEFAULT_EXPIRE_SECONDS),
|
||||
expire_seconds: clamp_public_asset_read_expire_seconds(
|
||||
Some(
|
||||
query
|
||||
.expire_seconds
|
||||
.unwrap_or(ASSET_READ_BYTES_DEFAULT_EXPIRE_SECONDS),
|
||||
),
|
||||
access_scope,
|
||||
),
|
||||
})
|
||||
.map_err(|error| map_oss_error(error, "aliyun-oss"))?;
|
||||
@@ -657,32 +673,20 @@ async fn authorize_asset_read_target(
|
||||
configured_bucket: &str,
|
||||
target: &AssetReadTarget,
|
||||
authorization: &AssetReadAuthorization,
|
||||
) -> Result<(), AppError> {
|
||||
) -> Result<AssetReadAccessScope, AppError> {
|
||||
if matches!(authorization, AssetReadAuthorization::Admin) {
|
||||
return Ok(());
|
||||
return Ok(AssetReadAccessScope::Privileged);
|
||||
}
|
||||
|
||||
let asset_object = state
|
||||
let (asset_object, public_work_granted) = state
|
||||
.spacetime_client()
|
||||
.get_asset_object_by_location(module_assets::AssetObjectLocationInput {
|
||||
.get_asset_read_access_by_location(module_assets::AssetObjectLocationInput {
|
||||
bucket: configured_bucket.to_string(),
|
||||
object_key: target.object_key.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(map_asset_read_authorization_error)?;
|
||||
if let Some(asset_object) = asset_object.as_ref() {
|
||||
let public_work_granted = if asset_object.access_policy == AssetObjectAccessPolicy::Private
|
||||
&& !asset_object_owner_matches(asset_object, authorization)
|
||||
&& asset_object_storage_matches(asset_object, configured_bucket, &target.object_key)
|
||||
{
|
||||
state
|
||||
.spacetime_client()
|
||||
.is_asset_object_referenced_by_public_work(asset_object.clone())
|
||||
.await
|
||||
.map_err(map_asset_read_authorization_error)?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
return require_asset_object_read_access(
|
||||
asset_object,
|
||||
configured_bucket,
|
||||
@@ -694,7 +698,7 @@ async fn authorize_asset_read_target(
|
||||
|
||||
// 已登记对象始终服从 metadata ACL;只有没有 metadata 的历史资源才走公开前缀兼容。
|
||||
if target.is_legacy_public_path && is_supported_legacy_public_object_key(&target.object_key) {
|
||||
return Ok(());
|
||||
return Ok(AssetReadAccessScope::Public);
|
||||
}
|
||||
|
||||
Err(asset_read_not_found())
|
||||
@@ -706,20 +710,34 @@ fn require_asset_object_read_access(
|
||||
object_key: &str,
|
||||
authorization: &AssetReadAuthorization,
|
||||
public_work_granted: bool,
|
||||
) -> Result<(), AppError> {
|
||||
) -> Result<AssetReadAccessScope, AppError> {
|
||||
if !asset_object_storage_matches(asset_object, configured_bucket, object_key) {
|
||||
return Err(asset_read_not_found());
|
||||
}
|
||||
if asset_object.access_policy == AssetObjectAccessPolicy::PublicRead
|
||||
|| public_work_granted
|
||||
|| asset_object_owner_matches(asset_object, authorization)
|
||||
{
|
||||
return Ok(());
|
||||
if asset_object_owner_matches(asset_object, authorization) {
|
||||
return Ok(AssetReadAccessScope::Privileged);
|
||||
}
|
||||
if asset_object.access_policy == AssetObjectAccessPolicy::PublicRead || public_work_granted {
|
||||
return Ok(AssetReadAccessScope::Public);
|
||||
}
|
||||
|
||||
Err(asset_read_not_found())
|
||||
}
|
||||
|
||||
fn clamp_public_asset_read_expire_seconds(
|
||||
requested_expire_seconds: Option<u64>,
|
||||
access_scope: AssetReadAccessScope,
|
||||
) -> Option<u64> {
|
||||
match access_scope {
|
||||
AssetReadAccessScope::Public => Some(
|
||||
requested_expire_seconds
|
||||
.unwrap_or(PUBLIC_ASSET_READ_MAX_EXPIRE_SECONDS)
|
||||
.min(PUBLIC_ASSET_READ_MAX_EXPIRE_SECONDS),
|
||||
),
|
||||
AssetReadAccessScope::Privileged => requested_expire_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
fn asset_object_storage_matches(
|
||||
asset_object: &module_assets::AssetObjectRecord,
|
||||
configured_bucket: &str,
|
||||
@@ -1017,16 +1035,16 @@ mod tests {
|
||||
Some("user-owner"),
|
||||
);
|
||||
|
||||
assert!(
|
||||
assert!(matches!(
|
||||
super::require_asset_object_read_access(
|
||||
&record,
|
||||
"genarrative-assets",
|
||||
record.object_key.as_str(),
|
||||
&super::AssetReadAuthorization::Owner("user-owner".to_string()),
|
||||
false,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
),
|
||||
Ok(super::AssetReadAccessScope::Privileged)
|
||||
));
|
||||
for authorization in [
|
||||
super::AssetReadAuthorization::Anonymous,
|
||||
super::AssetReadAuthorization::Owner("user-other".to_string()),
|
||||
@@ -1047,16 +1065,16 @@ mod tests {
|
||||
fn public_asset_read_allows_anonymous_but_rejects_storage_mismatch() {
|
||||
let record = asset_object_record(module_assets::AssetObjectAccessPolicy::PublicRead, None);
|
||||
|
||||
assert!(
|
||||
assert!(matches!(
|
||||
super::require_asset_object_read_access(
|
||||
&record,
|
||||
"genarrative-assets",
|
||||
record.object_key.as_str(),
|
||||
&super::AssetReadAuthorization::Anonymous,
|
||||
false,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
),
|
||||
Ok(super::AssetReadAccessScope::Public)
|
||||
));
|
||||
assert_eq!(
|
||||
super::require_asset_object_read_access(
|
||||
&record,
|
||||
@@ -1078,16 +1096,16 @@ mod tests {
|
||||
Some("user-owner"),
|
||||
);
|
||||
|
||||
assert!(
|
||||
assert!(matches!(
|
||||
super::require_asset_object_read_access(
|
||||
&record,
|
||||
"genarrative-assets",
|
||||
record.object_key.as_str(),
|
||||
&super::AssetReadAuthorization::Anonymous,
|
||||
true,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
),
|
||||
Ok(super::AssetReadAccessScope::Public)
|
||||
));
|
||||
assert_eq!(
|
||||
super::require_asset_object_read_access(
|
||||
&record,
|
||||
@@ -1102,6 +1120,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_asset_read_url_expiry_is_capped_but_privileged_expiry_is_preserved() {
|
||||
assert_eq!(
|
||||
super::clamp_public_asset_read_expire_seconds(
|
||||
None,
|
||||
super::AssetReadAccessScope::Public,
|
||||
),
|
||||
Some(super::PUBLIC_ASSET_READ_MAX_EXPIRE_SECONDS)
|
||||
);
|
||||
assert_eq!(
|
||||
super::clamp_public_asset_read_expire_seconds(
|
||||
Some(86_400),
|
||||
super::AssetReadAccessScope::Public,
|
||||
),
|
||||
Some(super::PUBLIC_ASSET_READ_MAX_EXPIRE_SECONDS)
|
||||
);
|
||||
assert_eq!(
|
||||
super::clamp_public_asset_read_expire_seconds(
|
||||
Some(86_400),
|
||||
super::AssetReadAccessScope::Privileged,
|
||||
),
|
||||
Some(86_400)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_public_fallback_only_accepts_curated_prefixes() {
|
||||
assert!(super::is_supported_legacy_public_object_key(
|
||||
|
||||
@@ -19,6 +19,15 @@ pub struct AssetObjectProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AssetObjectReadAccessProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<AssetObjectUpsertSnapshot>,
|
||||
pub public_work_granted: bool,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AssetHistoryListResult {
|
||||
|
||||
@@ -10,7 +10,8 @@ mod asset_object_service;
|
||||
|
||||
pub use application::{
|
||||
AssetEntityBindingProcedureResult, AssetHistoryListResult, AssetObjectProcedureResult,
|
||||
ConfirmAssetObjectResult, build_asset_entity_binding_input, build_asset_object_upsert_input,
|
||||
AssetObjectReadAccessProcedureResult, ConfirmAssetObjectResult,
|
||||
build_asset_entity_binding_input, build_asset_object_upsert_input,
|
||||
};
|
||||
#[cfg(feature = "server-service")]
|
||||
pub use asset_object_service::{
|
||||
|
||||
@@ -90,43 +90,25 @@ impl SpacetimeClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_asset_object_referenced_by_public_work(
|
||||
pub async fn get_asset_read_access_by_location(
|
||||
&self,
|
||||
asset_object: AssetObjectRecord,
|
||||
) -> Result<bool, SpacetimeClientError> {
|
||||
let owner_user_id = asset_object.owner_user_id.clone().unwrap_or_default();
|
||||
let grant_candidates = [
|
||||
module_assets::PublicAssetReadGrant {
|
||||
owner_user_id: owner_user_id.clone(),
|
||||
asset_object_id: Some(asset_object.asset_object_id.clone()),
|
||||
object_key: None,
|
||||
},
|
||||
module_assets::PublicAssetReadGrant {
|
||||
owner_user_id,
|
||||
asset_object_id: None,
|
||||
object_key: Some(asset_object.object_key.clone()),
|
||||
},
|
||||
];
|
||||
self.read_after_connect(
|
||||
"is_asset_object_referenced_by_public_work",
|
||||
move |connection| {
|
||||
let grants = connection.db().public_work_asset_read_grant();
|
||||
Ok(grant_candidates.iter().any(|candidate| {
|
||||
let Some(grant_id) = module_assets::public_asset_read_grant_id(candidate)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
grants.grant_id().find(&grant_id).is_some_and(|row| {
|
||||
module_assets::asset_object_matches_public_read_grant(
|
||||
&asset_object,
|
||||
&module_assets::PublicAssetReadGrant {
|
||||
owner_user_id: row.owner_user_id,
|
||||
asset_object_id: row.asset_object_id,
|
||||
object_key: row.object_key,
|
||||
},
|
||||
)
|
||||
})
|
||||
}))
|
||||
input: module_assets::AssetObjectLocationInput,
|
||||
) -> Result<(Option<AssetObjectRecord>, bool), SpacetimeClientError> {
|
||||
let procedure_input = input.into();
|
||||
self.call_after_connect(
|
||||
"get_asset_read_access_by_location_and_return",
|
||||
move |connection, sender| {
|
||||
connection
|
||||
.procedures()
|
||||
.get_asset_read_access_by_location_and_return_then(
|
||||
procedure_input,
|
||||
move |_, result| {
|
||||
let mapped = result
|
||||
.map_err(SpacetimeClientError::from_sdk_error)
|
||||
.and_then(map_asset_read_access_procedure_result);
|
||||
send_once(&sender, mapped);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -383,10 +383,9 @@ pub enum SpacetimeClientError {
|
||||
const DEFAULT_PROCEDURE_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const PUBLIC_WORK_PLAY_DAY_MICROS: i64 = 86_400_000_000;
|
||||
const PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS: i64 = 7;
|
||||
const REQUIRED_CACHED_READ_MODEL_QUERIES: [&str; 13] = [
|
||||
const REQUIRED_CACHED_READ_MODEL_QUERIES: [&str; 12] = [
|
||||
"SELECT * FROM public_work_gallery_entry",
|
||||
"SELECT * FROM public_work_detail_entry",
|
||||
"SELECT * FROM public_work_asset_read_grant",
|
||||
"SELECT * FROM bark_battle_gallery_view",
|
||||
"SELECT * FROM puzzle_gallery_card_view",
|
||||
"SELECT * FROM puzzle_clear_gallery_card_view",
|
||||
@@ -1205,9 +1204,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn required_read_model_subscriptions_include_public_asset_grants() {
|
||||
fn asset_acl_truth_is_not_read_from_cached_subscriptions() {
|
||||
assert!(
|
||||
REQUIRED_CACHED_READ_MODEL_QUERIES
|
||||
!REQUIRED_CACHED_READ_MODEL_QUERIES
|
||||
.contains(&"SELECT * FROM public_work_asset_read_grant")
|
||||
);
|
||||
assert!(!REQUIRED_CACHED_READ_MODEL_QUERIES.contains(&"SELECT * FROM asset_object"));
|
||||
|
||||
@@ -191,8 +191,8 @@ pub use self::wooden_fish::{
|
||||
|
||||
pub(crate) use self::ai::map_ai_task_procedure_result;
|
||||
pub(crate) use self::assets::{
|
||||
map_entity_binding_procedure_result, map_optional_asset_object_procedure_result,
|
||||
map_procedure_result,
|
||||
map_asset_read_access_procedure_result, map_entity_binding_procedure_result,
|
||||
map_optional_asset_object_procedure_result, map_procedure_result,
|
||||
};
|
||||
pub(crate) use self::auth::{
|
||||
map_auth_store_projection_procedure_result, map_auth_store_projection_sync_procedure_result,
|
||||
|
||||
@@ -82,6 +82,22 @@ pub(crate) fn map_optional_asset_object_procedure_result(
|
||||
.map(build_asset_object_record))
|
||||
}
|
||||
|
||||
pub(crate) fn map_asset_read_access_procedure_result(
|
||||
result: AssetObjectReadAccessProcedureResult,
|
||||
) -> Result<(Option<AssetObjectRecord>, bool), SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok((
|
||||
result
|
||||
.record
|
||||
.map(map_snapshot)
|
||||
.map(build_asset_object_record),
|
||||
result.public_work_granted,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod asset_object_procedure_result_tests {
|
||||
use super::*;
|
||||
@@ -103,6 +119,19 @@ mod asset_object_procedure_result_tests {
|
||||
});
|
||||
assert!(failed.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_access_lookup_preserves_authoritative_public_grant() {
|
||||
let result = map_asset_read_access_procedure_result(AssetObjectReadAccessProcedureResult {
|
||||
ok: true,
|
||||
record: None,
|
||||
public_work_granted: true,
|
||||
error_message: None,
|
||||
})
|
||||
.expect("authoritative read access lookup should succeed");
|
||||
|
||||
assert_eq!(result, (None, true));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_entity_binding_procedure_result(
|
||||
|
||||
@@ -96,6 +96,7 @@ pub mod asset_history_list_result_type;
|
||||
pub mod asset_object_access_policy_type;
|
||||
pub mod asset_object_location_input_type;
|
||||
pub mod asset_object_procedure_result_type;
|
||||
pub mod asset_object_read_access_procedure_result_type;
|
||||
pub mod asset_object_table;
|
||||
pub mod asset_object_type;
|
||||
pub mod asset_object_upsert_input_type;
|
||||
@@ -519,6 +520,7 @@ pub mod finish_wooden_fish_run_procedure;
|
||||
pub mod generate_big_fish_asset_procedure;
|
||||
pub mod get_asset_object_by_id_and_return_procedure;
|
||||
pub mod get_asset_object_by_location_and_return_procedure;
|
||||
pub mod get_asset_read_access_by_location_and_return_procedure;
|
||||
pub mod get_bark_battle_run_procedure;
|
||||
pub mod get_bark_battle_runtime_config_procedure;
|
||||
pub mod get_battle_state_procedure;
|
||||
@@ -1439,6 +1441,7 @@ pub use asset_history_list_result_type::AssetHistoryListResult;
|
||||
pub use asset_object_access_policy_type::AssetObjectAccessPolicy;
|
||||
pub use asset_object_location_input_type::AssetObjectLocationInput;
|
||||
pub use asset_object_procedure_result_type::AssetObjectProcedureResult;
|
||||
pub use asset_object_read_access_procedure_result_type::AssetObjectReadAccessProcedureResult;
|
||||
pub use asset_object_table::*;
|
||||
pub use asset_object_type::AssetObject;
|
||||
pub use asset_object_upsert_input_type::AssetObjectUpsertInput;
|
||||
@@ -1862,6 +1865,7 @@ pub use finish_wooden_fish_run_procedure::finish_wooden_fish_run;
|
||||
pub use generate_big_fish_asset_procedure::generate_big_fish_asset;
|
||||
pub use get_asset_object_by_id_and_return_procedure::get_asset_object_by_id_and_return;
|
||||
pub use get_asset_object_by_location_and_return_procedure::get_asset_object_by_location_and_return;
|
||||
pub use get_asset_read_access_by_location_and_return_procedure::get_asset_read_access_by_location_and_return;
|
||||
pub use get_bark_battle_run_procedure::get_bark_battle_run;
|
||||
pub use get_bark_battle_runtime_config_procedure::get_bark_battle_runtime_config;
|
||||
pub use get_battle_state_procedure::get_battle_state;
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_upsert_snapshot_type::AssetObjectUpsertSnapshot;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
pub struct AssetObjectReadAccessProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<AssetObjectUpsertSnapshot>,
|
||||
pub public_work_granted: bool,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for AssetObjectReadAccessProcedureResult {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||
|
||||
#![allow(unused, clippy::all)]
|
||||
use spacetimedb_sdk::__codegen::{self as __sdk, __lib, __sats, __ws};
|
||||
|
||||
use super::asset_object_location_input_type::AssetObjectLocationInput;
|
||||
use super::asset_object_read_access_procedure_result_type::AssetObjectReadAccessProcedureResult;
|
||||
|
||||
#[derive(__lib::ser::Serialize, __lib::de::Deserialize, Clone, PartialEq, Debug)]
|
||||
#[sats(crate = __lib)]
|
||||
struct GetAssetReadAccessByLocationAndReturnArgs {
|
||||
pub input: AssetObjectLocationInput,
|
||||
}
|
||||
|
||||
impl __sdk::InModule for GetAssetReadAccessByLocationAndReturnArgs {
|
||||
type Module = super::RemoteModule;
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
/// Extension trait for access to the procedure `get_asset_read_access_by_location_and_return`.
|
||||
///
|
||||
/// Implemented for [`super::RemoteProcedures`].
|
||||
pub trait get_asset_read_access_by_location_and_return {
|
||||
fn get_asset_read_access_by_location_and_return(&self, input: AssetObjectLocationInput) {
|
||||
self.get_asset_read_access_by_location_and_return_then(input, |_, _| {});
|
||||
}
|
||||
|
||||
fn get_asset_read_access_by_location_and_return_then(
|
||||
&self,
|
||||
input: AssetObjectLocationInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AssetObjectReadAccessProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
impl get_asset_read_access_by_location_and_return for super::RemoteProcedures {
|
||||
fn get_asset_read_access_by_location_and_return_then(
|
||||
&self,
|
||||
input: AssetObjectLocationInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<AssetObjectReadAccessProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, AssetObjectReadAccessProcedureResult>(
|
||||
"get_asset_read_access_by_location_and_return",
|
||||
GetAssetReadAccessByLocationAndReturnArgs { input },
|
||||
__callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,38 @@ pub fn get_asset_object_by_location_and_return(
|
||||
}
|
||||
}
|
||||
|
||||
// 公开授权与资产 metadata 必须在同一事务快照中判断,不能依赖 API 连接池的订阅水位。
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_asset_read_access_by_location_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
input: AssetObjectLocationInput,
|
||||
) -> AssetObjectReadAccessProcedureResult {
|
||||
let caller = ctx.sender();
|
||||
match ctx.try_with_tx(|tx| {
|
||||
crate::editor_project_storage::require_editor_generation_runtime_service_identity(
|
||||
tx, caller,
|
||||
)?;
|
||||
let record = find_asset_object_by_location(tx, &input)?;
|
||||
let public_work_granted = record.as_ref().is_some_and(|asset_object| {
|
||||
crate::public_asset_access::asset_object_has_public_work_read_grant(tx, asset_object)
|
||||
});
|
||||
Ok((record, public_work_granted))
|
||||
}) {
|
||||
Ok((record, public_work_granted)) => AssetObjectReadAccessProcedureResult {
|
||||
ok: true,
|
||||
record,
|
||||
public_work_granted,
|
||||
error_message: None,
|
||||
},
|
||||
Err(message) => AssetObjectReadAccessProcedureResult {
|
||||
ok: false,
|
||||
record: None,
|
||||
public_work_granted: false,
|
||||
error_message: Some(message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[spacetimedb::procedure]
|
||||
pub fn get_asset_object_by_id_and_return(
|
||||
ctx: &mut ProcedureContext,
|
||||
|
||||
@@ -240,6 +240,27 @@ pub fn public_work_asset_read_grant(ctx: &AnonymousViewContext) -> Vec<PublicWor
|
||||
grants.into_values().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn asset_object_has_public_work_read_grant(
|
||||
ctx: &ReducerContext,
|
||||
asset_object: &AssetObjectUpsertSnapshot,
|
||||
) -> bool {
|
||||
let asset_object = module_assets::build_asset_object_record(asset_object.clone());
|
||||
let view_context = ctx.as_anonymous_read_only();
|
||||
|
||||
public_work_asset_read_grant(&view_context)
|
||||
.into_iter()
|
||||
.any(|grant| {
|
||||
module_assets::asset_object_matches_public_read_grant(
|
||||
&asset_object,
|
||||
&module_assets::PublicAssetReadGrant {
|
||||
owner_user_id: grant.owner_user_id,
|
||||
asset_object_id: grant.asset_object_id,
|
||||
object_key: grant.object_key,
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_json_grants(
|
||||
grants: &mut BTreeMap<String, PublicWorkAssetReadGrant>,
|
||||
scope: &PublicWorkAssetGrantScope<'_>,
|
||||
|
||||
Reference in New Issue
Block a user