use database::enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole, WorkspaceSharing}; use database::pool::get_pg_pool; use database::schema::{asset_permissions, dashboard_files, metric_files_to_dashboard_files, collections, collections_to_assets, chats}; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, OptionalExtension}; use diesel_async::RunQueryDsl; use middleware::OrganizationMembership; use uuid::Uuid; use std::cmp::Ordering; /// Computes the effective permission level for a user on an asset by taking the maximum /// of their direct permission and workspace sharing permission. /// /// # Arguments /// * `direct_permission` - The user's direct permission on the asset (if any) /// * `workspace_sharing` - The workspace sharing level for the asset /// * `organization_id` - UUID of the organization /// * `organization_role_grants` - User's organization memberships /// /// # Returns /// * `Option` - The highest permission level available to the user pub fn compute_effective_permission( direct_permission: Option, workspace_sharing: WorkspaceSharing, organization_id: Uuid, organization_role_grants: &[OrganizationMembership], ) -> Option { // First check if the user has WorkspaceAdmin or DataAdmin role for the organization for org in organization_role_grants { if org.id == organization_id && (org.role == UserOrganizationRole::WorkspaceAdmin || org.role == UserOrganizationRole::DataAdmin) { return Some(AssetPermissionRole::Owner); } } // Compute workspace-granted permission let workspace_permission = if workspace_sharing != WorkspaceSharing::None { // Check if user is member of the organization if organization_role_grants.iter().any(|org| org.id == organization_id) { match workspace_sharing { WorkspaceSharing::CanView => Some(AssetPermissionRole::CanView), WorkspaceSharing::CanEdit => Some(AssetPermissionRole::CanEdit), WorkspaceSharing::FullAccess => Some(AssetPermissionRole::FullAccess), WorkspaceSharing::None => None, } } else { None } } else { None }; // Return the highest permission level match (direct_permission, workspace_permission) { (Some(direct), Some(workspace)) => { // Use the max method to get the higher permission Some(direct.max(workspace)) } (Some(direct), None) => Some(direct), (None, Some(workspace)) => Some(workspace), (None, None) => None, } } /// Checks if a user has sufficient permissions based on organization roles and asset permissions. /// /// # Arguments /// * `current_permission_level` - Optional current permission level of the user for the asset /// * `required_permission_level` - Required permission level to access the asset /// * `organization_id` - UUID of the organization /// * `organization_role_grants` - Array of tuples containing (UUID, UserOrganizationRole) for the user /// * `workspace_sharing` - Workspace sharing level for the asset /// /// # Returns /// * `bool` - True if the user has sufficient permissions, false otherwise pub fn check_permission_access( current_permission_level: Option, required_permission_level: &[AssetPermissionRole], organization_id: Uuid, organization_role_grants: &[OrganizationMembership], workspace_sharing: WorkspaceSharing, ) -> bool { // First check if the user has WorkspaceAdmin or DataAdmin role for the organization for org in organization_role_grants { if org.id == organization_id && (org.role == UserOrganizationRole::WorkspaceAdmin || org.role == UserOrganizationRole::DataAdmin) { return true; } } // Check if user is member of the workspace and asset is shared if workspace_sharing != WorkspaceSharing::None { for org in organization_role_grants { if org.id == organization_id { // Map workspace sharing level to permission role let workspace_permission = match workspace_sharing { WorkspaceSharing::CanView => AssetPermissionRole::CanView, WorkspaceSharing::CanEdit => AssetPermissionRole::CanEdit, WorkspaceSharing::FullAccess => AssetPermissionRole::FullAccess, WorkspaceSharing::None => unreachable!(), }; if required_permission_level.contains(&workspace_permission) { return true; } } } } // Then check if the user has the required permission level if let Some(permission) = current_permission_level { if required_permission_level.contains(&permission) { return true; } } // If none of the above conditions are met, return false false } /// Checks if a user has access to a metric through any associated dashboard. /// /// This function is used to implement permission cascading from dashboards to metrics. /// If a user has access to any dashboard containing the metric (either through direct permissions, /// workspace sharing, or if the dashboard is public), they get at least CanView permission. /// This also checks if the dashboard itself is accessible via collections (transitive cascading). /// /// # Arguments /// * `metric_id` - UUID of the metric to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any dashboard containing the metric, false otherwise pub async fn check_metric_dashboard_access( metric_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // Get all dashboards containing this metric let dashboard_ids: Vec = metric_files_to_dashboard_files::table .inner_join( dashboard_files::table .on(dashboard_files::id.eq(metric_files_to_dashboard_files::dashboard_file_id)), ) .filter(metric_files_to_dashboard_files::metric_file_id.eq(metric_id)) .filter(dashboard_files::deleted_at.is_null()) .filter(metric_files_to_dashboard_files::deleted_at.is_null()) .select(dashboard_files::id) .load::(&mut conn) .await?; if dashboard_ids.is_empty() { return Ok(false); } // Check multiple access paths concurrently let user_id_clone = *user_id; let user_orgs_clone = user_orgs.to_vec(); let dashboard_ids_clone = dashboard_ids.clone(); // 1. Check direct access to dashboards let direct_access_future = async move { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; let has_direct = asset_permissions::table .filter(asset_permissions::asset_id.eq_any(&dashboard_ids_clone)) .filter(asset_permissions::asset_type.eq(AssetType::DashboardFile)) .filter(asset_permissions::identity_id.eq(user_id_clone)) .filter(asset_permissions::identity_type.eq(IdentityType::User)) .filter(asset_permissions::deleted_at.is_null()) .select(asset_permissions::asset_id) .first::(&mut conn) .await .optional()?; Ok::(has_direct.is_some()) }; // 2. Check public access to dashboards let dashboard_ids_public = dashboard_ids.clone(); let public_access_future = async move { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; let now = chrono::Utc::now(); let has_public = dashboard_files::table .filter(dashboard_files::id.eq_any(&dashboard_ids_public)) .filter(dashboard_files::deleted_at.is_null()) .filter(dashboard_files::publicly_accessible.eq(true)) .filter( dashboard_files::public_expiry_date .is_null() .or(dashboard_files::public_expiry_date.gt(now)), ) .select(dashboard_files::id) .first::(&mut conn) .await .optional()?; Ok::(has_public.is_some()) }; // 3. Check workspace sharing on dashboards let dashboard_ids_ws = dashboard_ids.clone(); let user_orgs_ws = user_orgs.to_vec(); let workspace_access_future = async move { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; let workspace_dashboards = dashboard_files::table .filter(dashboard_files::id.eq_any(&dashboard_ids_ws)) .filter(dashboard_files::deleted_at.is_null()) .filter(dashboard_files::workspace_sharing.ne(WorkspaceSharing::None)) .select((dashboard_files::organization_id, dashboard_files::workspace_sharing)) .load::<(Uuid, WorkspaceSharing)>(&mut conn) .await?; for (org_id, _) in workspace_dashboards { if user_orgs_ws.iter().any(|org| org.id == org_id) { return Ok::(true); } } Ok::(false) }; // 4. Check if dashboards are accessible via collections let dashboard_ids_coll = dashboard_ids.clone(); let user_id_coll = *user_id; let user_orgs_coll = user_orgs.to_vec(); let collection_access_future = async move { for dashboard_id in &dashboard_ids_coll { if check_dashboard_collection_access(&dashboard_id, &user_id_coll, &user_orgs_coll).await? { return Ok::(true); } } Ok::(false) }; // Execute all checks concurrently let (direct_result, public_result, workspace_result, collection_result) = tokio::join!( direct_access_future, public_access_future, workspace_access_future, collection_access_future ); // Return true if any check succeeds Ok(direct_result? || public_result? || workspace_result? || collection_result?) } /// Checks if a user has access to a metric through any associated chat. /// /// This function is used to implement permission cascading from chats to metrics. /// If a user has access to any chat containing the metric (either through direct permissions, /// workspace sharing, or if the chat is public), they get at least CanView permission. /// /// # Arguments /// * `metric_id` - UUID of the metric to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any chat containing the metric, false otherwise pub async fn check_metric_chat_access( metric_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // Check if user has access to any chat containing this metric let has_chat_access = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .inner_join( asset_permissions::table.on( asset_permissions::asset_id.eq(database::schema::chats::id) .and(asset_permissions::asset_type.eq(AssetType::Chat)) .and(asset_permissions::identity_id.eq(user_id)) .and(asset_permissions::identity_type.eq(IdentityType::User)) .and(asset_permissions::deleted_at.is_null()) ), ) .filter(database::schema::messages_to_files::file_id.eq(metric_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .select(database::schema::chats::id) .first::(&mut conn) .await .optional()?; if has_chat_access.is_some() { return Ok(true); } // Now check if metric belongs to any PUBLIC chat let now = chrono::Utc::now(); let has_public_chat_access = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .filter(database::schema::messages_to_files::file_id.eq(metric_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .filter(database::schema::chats::publicly_accessible.eq(true)) .filter( database::schema::chats::public_expiry_date .is_null() .or(database::schema::chats::public_expiry_date.gt(now)), ) .select(database::schema::chats::id) .first::(&mut conn) .await .optional()?; if has_public_chat_access.is_some() { return Ok(true); } // Check if metric belongs to any workspace-shared chat let workspace_shared_chat = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .filter(database::schema::messages_to_files::file_id.eq(metric_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .filter(database::schema::chats::workspace_sharing.ne(WorkspaceSharing::None)) .select((database::schema::chats::organization_id, database::schema::chats::workspace_sharing)) .first::<(Uuid, WorkspaceSharing)>(&mut conn) .await .optional()?; if let Some((org_id, _sharing_level)) = workspace_shared_chat { // Check if user is member of that organization if user_orgs.iter().any(|org| org.id == org_id) { return Ok(true); } } Ok(false) } /// Checks if a user has access to a dashboard through any associated chat. /// /// This function is used to implement permission cascading from chats to dashboards. /// If a user has access to any chat containing the dashboard (either through direct permissions, /// workspace sharing, or if the chat is public), they get at least CanView permission. /// /// # Arguments /// * `dashboard_id` - UUID of the dashboard to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any chat containing the dashboard, false otherwise pub async fn check_dashboard_chat_access( dashboard_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // Check if user has access to any chat containing this dashboard let has_chat_access = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .inner_join( asset_permissions::table.on( asset_permissions::asset_id.eq(database::schema::chats::id) .and(asset_permissions::asset_type.eq(AssetType::Chat)) .and(asset_permissions::identity_id.eq(user_id)) .and(asset_permissions::identity_type.eq(IdentityType::User)) .and(asset_permissions::deleted_at.is_null()) ), ) .filter(database::schema::messages_to_files::file_id.eq(dashboard_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .select(database::schema::chats::id) .first::(&mut conn) .await .optional()?; if has_chat_access.is_some() { return Ok(true); } // Now check if dashboard belongs to any PUBLIC chat let now = chrono::Utc::now(); let has_public_chat_access = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .filter(database::schema::messages_to_files::file_id.eq(dashboard_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .filter(database::schema::chats::publicly_accessible.eq(true)) .filter( database::schema::chats::public_expiry_date .is_null() .or(database::schema::chats::public_expiry_date.gt(now)), ) .select(database::schema::chats::id) .first::(&mut conn) .await .optional()?; if has_public_chat_access.is_some() { return Ok(true); } // Check if dashboard belongs to any workspace-shared chat let workspace_shared_chat = database::schema::messages_to_files::table .inner_join( database::schema::messages::table .on(database::schema::messages::id.eq(database::schema::messages_to_files::message_id)), ) .inner_join( database::schema::chats::table .on(database::schema::chats::id.eq(database::schema::messages::chat_id)), ) .filter(database::schema::messages_to_files::file_id.eq(dashboard_id)) .filter(database::schema::messages_to_files::deleted_at.is_null()) .filter(database::schema::messages::deleted_at.is_null()) .filter(database::schema::chats::deleted_at.is_null()) .filter(database::schema::chats::workspace_sharing.ne(WorkspaceSharing::None)) .select((database::schema::chats::organization_id, database::schema::chats::workspace_sharing)) .first::<(Uuid, WorkspaceSharing)>(&mut conn) .await .optional()?; if let Some((org_id, _sharing_level)) = workspace_shared_chat { // Check if user is member of that organization if user_orgs.iter().any(|org| org.id == org_id) { return Ok(true); } } Ok(false) } /// Checks if a user has access to a metric through any associated collection. /// /// This function is used to implement permission cascading from collections to metrics. /// If a user has access to any collection containing the metric (either through direct permissions, /// workspace sharing, or if the collection is public), they get at least CanView permission. /// /// # Arguments /// * `metric_id` - UUID of the metric to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any collection containing the metric, false otherwise pub async fn check_metric_collection_access( metric_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // First check if user has direct access to any collection containing this metric let has_direct_access = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .inner_join( asset_permissions::table.on( asset_permissions::asset_id.eq(collections::id) .and(asset_permissions::asset_type.eq(AssetType::Collection)) .and(asset_permissions::identity_id.eq(user_id)) .and(asset_permissions::identity_type.eq(IdentityType::User)) .and(asset_permissions::deleted_at.is_null()) ), ) .filter(collections_to_assets::asset_id.eq(metric_id)) .filter(collections_to_assets::asset_type.eq(AssetType::MetricFile)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .select(collections::id) .first::(&mut conn) .await .optional()?; if has_direct_access.is_some() { return Ok(true); } // Note: Collections don't have publicly_accessible fields, only workspace_sharing // Check if metric belongs to any workspace-shared collection let workspace_shared_collection = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .filter(collections_to_assets::asset_id.eq(metric_id)) .filter(collections_to_assets::asset_type.eq(AssetType::MetricFile)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) .select((collections::organization_id, collections::workspace_sharing)) .first::<(Uuid, WorkspaceSharing)>(&mut conn) .await .optional()?; if let Some((org_id, _sharing_level)) = workspace_shared_collection { // Check if user is member of that organization if user_orgs.iter().any(|org| org.id == org_id) { return Ok(true); } } Ok(false) } /// Checks if a user has access to a dashboard through any associated collection. /// /// This function is used to implement permission cascading from collections to dashboards. /// If a user has access to any collection containing the dashboard (either through direct permissions, /// workspace sharing, or if the collection is public), they get at least CanView permission. /// /// # Arguments /// * `dashboard_id` - UUID of the dashboard to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any collection containing the dashboard, false otherwise pub async fn check_dashboard_collection_access( dashboard_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // First check if user has direct access to any collection containing this dashboard let has_direct_access = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .inner_join( asset_permissions::table.on( asset_permissions::asset_id.eq(collections::id) .and(asset_permissions::asset_type.eq(AssetType::Collection)) .and(asset_permissions::identity_id.eq(user_id)) .and(asset_permissions::identity_type.eq(IdentityType::User)) .and(asset_permissions::deleted_at.is_null()) ), ) .filter(collections_to_assets::asset_id.eq(dashboard_id)) .filter(collections_to_assets::asset_type.eq(AssetType::DashboardFile)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .select(collections::id) .first::(&mut conn) .await .optional()?; if has_direct_access.is_some() { return Ok(true); } // Note: Collections don't have publicly_accessible fields, only workspace_sharing // Check if dashboard belongs to any workspace-shared collection let workspace_shared_collection = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .filter(collections_to_assets::asset_id.eq(dashboard_id)) .filter(collections_to_assets::asset_type.eq(AssetType::DashboardFile)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) .select((collections::organization_id, collections::workspace_sharing)) .first::<(Uuid, WorkspaceSharing)>(&mut conn) .await .optional()?; if let Some((org_id, _sharing_level)) = workspace_shared_collection { // Check if user is member of that organization if user_orgs.iter().any(|org| org.id == org_id) { return Ok(true); } } Ok(false) } /// Checks if a user has access to a chat through any associated collection. /// /// This function is used to implement permission cascading from collections to chats. /// If a user has access to any collection containing the chat (either through direct permissions /// or workspace sharing), they get at least CanView permission. /// /// # Arguments /// * `chat_id` - UUID of the chat to check /// * `user_id` - UUID of the user to check permissions for /// * `user_orgs` - User's organization memberships /// /// # Returns /// * `Result` - True if the user has access to any collection containing the chat, false otherwise pub async fn check_chat_collection_access( chat_id: &Uuid, user_id: &Uuid, user_orgs: &[OrganizationMembership], ) -> Result { let mut conn = get_pg_pool().get().await.map_err(|e| { diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UnableToSendCommand, Box::new(e.to_string()), ) })?; // First check if user has direct access to any collection containing this chat let has_direct_access = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .inner_join( asset_permissions::table.on( asset_permissions::asset_id.eq(collections::id) .and(asset_permissions::asset_type.eq(AssetType::Collection)) .and(asset_permissions::identity_id.eq(user_id)) .and(asset_permissions::identity_type.eq(IdentityType::User)) .and(asset_permissions::deleted_at.is_null()) ), ) .filter(collections_to_assets::asset_id.eq(chat_id)) .filter(collections_to_assets::asset_type.eq(AssetType::Chat)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .select(collections::id) .first::(&mut conn) .await .optional()?; if has_direct_access.is_some() { return Ok(true); } // Check if chat belongs to any workspace-shared collection let workspace_shared_collection = collections_to_assets::table .inner_join( collections::table .on(collections::id.eq(collections_to_assets::collection_id)), ) .filter(collections_to_assets::asset_id.eq(chat_id)) .filter(collections_to_assets::asset_type.eq(AssetType::Chat)) .filter(collections::deleted_at.is_null()) .filter(collections_to_assets::deleted_at.is_null()) .filter(collections::workspace_sharing.ne(WorkspaceSharing::None)) .select((collections::organization_id, collections::workspace_sharing)) .first::<(Uuid, WorkspaceSharing)>(&mut conn) .await .optional()?; if let Some((org_id, _sharing_level)) = workspace_shared_collection { // Check if user is member of that organization if user_orgs.iter().any(|org| org.id == org_id) { return Ok(true); } } Ok(false) } #[cfg(test)] mod tests { use super::*; #[test] fn test_workspace_admin_access() { let org_id = Uuid::new_v4(); let grants = vec![OrganizationMembership { id: org_id, role: UserOrganizationRole::WorkspaceAdmin }]; assert!(check_permission_access( None, &[AssetPermissionRole::Owner], org_id, &grants, WorkspaceSharing::None )); } #[test] fn test_data_admin_access() { let org_id = Uuid::new_v4(); let grants = vec![OrganizationMembership { id: org_id, role: UserOrganizationRole::DataAdmin }]; assert!(check_permission_access( None, &[AssetPermissionRole::Owner], org_id, &grants, WorkspaceSharing::None )); } #[test] fn test_matching_permission_level() { let org_id = Uuid::new_v4(); let grants = vec![OrganizationMembership { id: org_id, role: UserOrganizationRole::Viewer }]; assert!(check_permission_access( Some(AssetPermissionRole::CanEdit), &[AssetPermissionRole::CanEdit], org_id, &grants, WorkspaceSharing::None )); } #[test] fn test_insufficient_permissions() { let org_id = Uuid::new_v4(); let grants = vec![OrganizationMembership { id: org_id, role: UserOrganizationRole::Viewer }]; assert!(!check_permission_access( Some(AssetPermissionRole::CanView), &[AssetPermissionRole::CanEdit], org_id, &grants, WorkspaceSharing::None )); } #[test] fn test_no_permissions() { let org_id = Uuid::new_v4(); let grants = vec![OrganizationMembership { id: org_id, role: UserOrganizationRole::Viewer }]; assert!(!check_permission_access( None, &[AssetPermissionRole::CanView], org_id, &grants, WorkspaceSharing::None )); } }