Files
ELY-Browser/crates/ely_servo_host/src/runtime_permissions_tests.rs

381 lines
11 KiB
Rust

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<SitePermissionFeature> = [
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>>
{
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<PermissionSnapshotEntry>,
) {
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<PermissionDecision> {
take_permission_decision(permissions, profile_id, origin.clone(), SitePermissionFeature::Camera)
}