Skip to main content

cloudillo_file/
filter.rs

1//! Visibility filtering for files
2
3use crate::prelude::*;
4use cloudillo_core::abac::can_view_item;
5use cloudillo_core::file_access;
6use cloudillo_types::meta_adapter::FileView;
7use cloudillo_types::types::AccessLevel;
8
9/// Filter files by visibility and compute access_level for each file
10///
11/// This function filters a list of files to only include those the subject
12/// is allowed to see based on:
13/// - The file's visibility level
14/// - The subject's relationship with the owner (following/connected)
15///
16/// For each visible file, it also computes the user's access level (Read/Write).
17pub async fn filter_files_by_visibility(
18	app: &App,
19	tn_id: TnId,
20	subject_id_tag: &str,
21	is_authenticated: bool,
22	tenant_id_tag: &str,
23	subject_roles: &[Box<str>],
24	files: Vec<FileView>,
25) -> ClResult<Vec<FileView>> {
26	// If no files, return early
27	if files.is_empty() {
28		return Ok(files);
29	}
30
31	// Look up subject's relationship with the tenant (the only relationship we can check)
32	let rels = app.meta_adapter.get_relationships(tn_id, &[subject_id_tag]).await?;
33	let (following, connected) = rels.get(subject_id_tag).copied().unwrap_or((false, false));
34
35	// Filter files based on visibility
36	let visible_files: Vec<FileView> = files
37		.into_iter()
38		.filter(|file| {
39			// Get owner id_tag, filtering out empty strings (from failed profile JOINs)
40			let owner_tag = file
41				.owner
42				.as_ref()
43				.and_then(|o| if o.id_tag.is_empty() { None } else { Some(o.id_tag.as_ref()) })
44				.unwrap_or(tenant_id_tag);
45
46			// Files don't have audience, so pass None
47			can_view_item(
48				subject_id_tag,
49				is_authenticated,
50				owner_tag,
51				tenant_id_tag,
52				file.visibility,
53				following,
54				connected,
55				None,
56			)
57		})
58		.collect();
59
60	// For anonymous users, access_level is Read for all visible files
61	if !is_authenticated || subject_id_tag.is_empty() {
62		return Ok(visible_files
63			.into_iter()
64			.map(|mut file| {
65				file.access_level = Some(AccessLevel::Read);
66				file
67			})
68			.collect());
69	}
70
71	// For authenticated users, compute access level for each file
72	let mut result = Vec::with_capacity(visible_files.len());
73	for mut file in visible_files {
74		// Get owner id_tag, filtering out empty strings (from failed profile JOINs)
75		let owner_tag = file
76			.owner
77			.as_ref()
78			.and_then(|o| if o.id_tag.is_empty() { None } else { Some(o.id_tag.as_ref()) })
79			.unwrap_or(tenant_id_tag);
80
81		let ctx = file_access::FileAccessCtx {
82			user_id_tag: subject_id_tag,
83			tenant_id_tag,
84			user_roles: subject_roles,
85		};
86		let access_level =
87			file_access::get_access_level(app, tn_id, &file.file_id, owner_tag, &ctx).await;
88
89		file.access_level = Some(access_level);
90		result.push(file);
91	}
92
93	Ok(result)
94}
95
96// vim: ts=4