use ely_domain::{ProfileId, SiteOrigin, SitePermissionFeature}; use crate::{ ConsumedPermission, PermissionDecision, PermissionSnapshotEntry, PermissionSnapshotState, runtime_permissions::{ PermissionStore, drain_consumed_permissions, replace_permission_decisions, site_permission_feature_for_servo, take_permission_decision, }, }; #[test] fn keeps_disabled_servo_permissions_out_of_site_settings() { for feature in [ servo::PermissionFeature::ScreenWakeLock(servo::WakeLockType::Screen), servo::PermissionFeature::Gamepad, ] { assert_eq!(site_permission_feature_for_servo(feature), None); } } /// The site-settings toggles come from `SitePermissionFeature::enforced()`. /// This pins that list to what the Servo permission mapping actually /// honors, so the UI can never drift back into offering placebo toggles. /// (The mapping match is exhaustive over `servo::PermissionFeature`, so a /// new upstream variant already fails to compile until it is mapped.) #[test] fn enforced_features_match_the_servo_mapping() { let mapped: Vec = [ servo::PermissionFeature::Camera, servo::PermissionFeature::Microphone, servo::PermissionFeature::Geolocation, servo::PermissionFeature::Notifications, servo::PermissionFeature::PersistentStorage, ] .into_iter() .filter_map(site_permission_feature_for_servo) .collect(); assert_eq!(mapped.as_slice(), SitePermissionFeature::enforced()); for feature in SitePermissionFeature::enforced() { assert!(SitePermissionFeature::all().contains(feature)); } for placebo in [ SitePermissionFeature::ClipboardRead, SitePermissionFeature::WebUsb, SitePermissionFeature::Popups, ] { assert!(!SitePermissionFeature::enforced().contains(&placebo)); } } #[test] fn allow_once_is_consumed_after_one_matching_request() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); assert_eq!(take(&permissions, &profile_id, &origin), Some(PermissionDecision::AllowOnce)); assert_eq!(take(&permissions, &profile_id, &origin), None); assert_eq!( drain_consumed_permissions(&permissions), vec![ConsumedPermission { profile_id, origin, feature: SitePermissionFeature::Camera, grant_revision: 1, }], ); Ok(()) } #[test] fn durable_permissions_stay_scoped_to_profile_origin_and_feature() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let other_profile = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowAlways, 1, )], ); assert_eq!(take(&permissions, &profile_id, &origin), Some(PermissionDecision::AllowAlways)); assert_eq!(take(&permissions, &other_profile, &origin), None); assert_eq!( take_permission_decision( &permissions, &profile_id, SiteOrigin::parse("https://other.test")?, SitePermissionFeature::Camera, ), None, ); Ok(()) } #[test] fn authoritative_snapshot_removes_missing_durable_entries() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let other_profile = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; let other_origin = SiteOrigin::parse("https://other.test")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowAlways, 1, )], ); replace( &permissions, &other_profile, 1, vec![decision( other_origin.clone(), SitePermissionFeature::Camera, PermissionDecision::DenyAlways, 1, )], ); replace(&permissions, &profile_id, 2, Vec::new()); assert_eq!(take(&permissions, &profile_id, &origin), None); assert_eq!( take(&permissions, &other_profile, &other_origin), Some(PermissionDecision::DenyAlways) ); Ok(()) } #[test] fn transferred_marker_preserves_existing_token_and_fails_closed_for_fresh_host() -> Result<(), Box> { let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; let marker = state( origin.clone(), SitePermissionFeature::Camera, PermissionSnapshotState::TransferredAllowOnce, 1, ); let live = PermissionStore::default(); replace( &live, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); replace(&live, &profile_id, 2, vec![marker.clone()]); let restarted = PermissionStore::default(); replace(&restarted, &profile_id, 2, vec![marker]); assert!(drain_consumed_permissions(&live).is_empty()); assert_eq!( drain_consumed_permissions(&restarted), vec![ConsumedPermission { profile_id: profile_id.clone(), origin: origin.clone(), feature: SitePermissionFeature::Camera, grant_revision: 1, }], ); assert_eq!(take(&live, &profile_id, &origin), Some(PermissionDecision::AllowOnce)); assert_eq!(take(&restarted, &profile_id, &origin), None); assert_eq!( drain_consumed_permissions(&live), vec![ConsumedPermission { profile_id, origin, feature: SitePermissionFeature::Camera, grant_revision: 1, }], ); Ok(()) } #[test] fn empty_snapshot_removes_transferred_allow_once() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); replace( &permissions, &profile_id, 2, vec![state( origin.clone(), SitePermissionFeature::Camera, PermissionSnapshotState::TransferredAllowOnce, 1, )], ); replace(&permissions, &profile_id, 3, Vec::new()); assert_eq!(take(&permissions, &profile_id, &origin), None); Ok(()) } #[test] fn mismatched_transferred_revision_consumes_the_replacement_token() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); replace( &permissions, &profile_id, 2, vec![state( origin.clone(), SitePermissionFeature::Camera, PermissionSnapshotState::TransferredAllowOnce, 2, )], ); assert_eq!(take(&permissions, &profile_id, &origin), None); assert_eq!( drain_consumed_permissions(&permissions), vec![ConsumedPermission { profile_id, origin, feature: SitePermissionFeature::Camera, grant_revision: 2, }], ); Ok(()) } #[test] fn stale_profile_generation_cannot_restore_revoked_permission() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; let old = decision(origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowAlways, 1); replace(&permissions, &profile_id, 1, vec![old.clone()]); replace(&permissions, &profile_id, 2, Vec::new()); replace(&permissions, &profile_id, 1, vec![old]); assert_eq!(take(&permissions, &profile_id, &origin), None); Ok(()) } #[test] fn same_revision_stays_consumed_and_new_revision_rearms_token() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; replace( &permissions, &profile_id, 1, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); assert_eq!(take(&permissions, &profile_id, &origin), Some(PermissionDecision::AllowOnce)); replace( &permissions, &profile_id, 2, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 1, )], ); assert_eq!(take(&permissions, &profile_id, &origin), None); replace( &permissions, &profile_id, 3, vec![decision( origin.clone(), SitePermissionFeature::Camera, PermissionDecision::AllowOnce, 3, )], ); assert_eq!(take(&permissions, &profile_id, &origin), Some(PermissionDecision::AllowOnce)); Ok(()) } fn replace( permissions: &PermissionStore, profile_id: &ProfileId, generation: u64, entries: Vec, ) { replace_permission_decisions(permissions, profile_id, generation, entries); } fn decision( origin: SiteOrigin, feature: SitePermissionFeature, decision: PermissionDecision, revision: u64, ) -> PermissionSnapshotEntry { state(origin, feature, PermissionSnapshotState::Decision(decision), revision) } fn state( origin: SiteOrigin, feature: SitePermissionFeature, state: PermissionSnapshotState, revision: u64, ) -> PermissionSnapshotEntry { PermissionSnapshotEntry { origin, feature, state, revision } } fn take( permissions: &PermissionStore, profile_id: &ProfileId, origin: &SiteOrigin, ) -> Option { take_permission_decision(permissions, profile_id, origin.clone(), SitePermissionFeature::Camera) }