feat(drive): add policy forbid_public_links

This commit is contained in:
Edouard Vanbelle
2026-06-26 01:32:59 +02:00
parent cfd783cbd3
commit ddb131da8b
10 changed files with 568 additions and 3 deletions
+46
View File
@@ -133,4 +133,50 @@ impl Drive {
pub fn is_personal(&self) -> bool {
matches!(self.kind, DriveKind::Personal)
}
/// Typed view of `policies` for enforcement code. Lenient deserialise:
/// unknown keys are preserved on disk (the column stays the canonical
/// JSONB bag) but ignored here, missing keys default to `false`.
/// See `docs/plan/drive.md` §8.
pub fn typed_policies(&self) -> DrivePolicies {
DrivePolicies::from_value(&self.policies)
}
}
/// Typed mirror of the `policies` JSONB. Five known keys; the JSONB column
/// remains the source of truth and may carry unknown keys verbatim — this
/// struct is a read view for enforcement and a write view for the policy
/// PATCH endpoint. Every field defaults to `false` (everything allowed)
/// so a freshly-created drive doesn't need a populated policy bag.
///
/// See `docs/plan/drive.md` §8 for the enforcement matrix
/// (which callsite each key is checked at).
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(default)]
pub struct DrivePolicies {
/// Disables per-resource grants on resources in this drive. Drive-level
/// membership (Owner/Editor/Viewer) still works. Enforced at
/// `grant_handler::create_grant`.
pub forbid_sharing: bool,
/// Blocks grants whose subject has `users.is_external = true`. Enforced
/// at `magic_link_invite_service::resolve_or_create_recipient` and
/// `grant_handler::create_grant`.
pub forbid_external_sharing: bool,
/// Blocks anonymous-link (token-share) creation on resources in this
/// drive. Enforced at `share_service::create_shared_link`.
pub forbid_public_links: bool,
/// Blocks MOVE when `src.drive_id != dst.drive_id`. Enforced at the
/// move endpoints. Lands paired with D6's cross-drive move work.
pub forbid_cross_drive_move: bool,
}
impl DrivePolicies {
/// Parse from the raw JSONB. Lenient — unknown keys are dropped from
/// the typed view but remain in the source `serde_json::Value`. A
/// malformed bag (e.g. wrong type) falls back to the all-false default
/// rather than refusing the read; enforcement code never panics on
/// existing data.
pub fn from_value(value: &serde_json::Value) -> Self {
serde_json::from_value(value.clone()).unwrap_or_default()
}
}
@@ -204,6 +204,41 @@ pub trait DriveRepository: Send + Sync + 'static {
/// necessarily a member, so the per-drive role would be misleading
/// here.
async fn list_all(&self) -> Result<Vec<DriveWithRootName>, DriveRepositoryError>;
/// Resolve a file's owning drive policies in one round-trip. Used by
/// D5 enforcement points (`forbid_public_links`, `forbid_sharing`, …)
/// to gate per-resource actions without a separate file-lookup +
/// drive-lookup pair.
///
/// Returns `NotFound` when the file id is gone or its `drive_id`
/// doesn't resolve to a drive row (a state the no-orphan triggers
/// prevent in production, but the caller should still propagate the
/// 404 cleanly).
async fn get_policies_for_file(
&self,
file_id: Uuid,
) -> Result<crate::domain::entities::drive::DrivePolicies, DriveRepositoryError>;
/// Resolve a folder's owning drive policies in one round-trip. Same
/// shape as [`Self::get_policies_for_file`].
async fn get_policies_for_folder(
&self,
folder_id: Uuid,
) -> Result<crate::domain::entities::drive::DrivePolicies, DriveRepositoryError>;
/// Merge the given partial policy bag into the drive's existing
/// `policies` JSONB, returning the updated bag. JSONB-level merge
/// preserves unknown keys already present on disk (the column stays
/// the canonical bag — see `DrivePolicies::from_value`). `caller_id`
/// is recorded for the audit log emitted at the service layer.
///
/// Caller is responsible for the `Manage` permission check; this
/// method does not re-verify.
async fn update_policies(
&self,
drive_id: Uuid,
partial: &crate::domain::entities::drive::DrivePolicies,
) -> Result<crate::domain::entities::drive::DrivePolicies, DriveRepositoryError>;
}
/// Convenience: convert the canonical kind discriminator from its SQL