feat(sync): include site permissions in snapshots
This commit is contained in:
@@ -35,6 +35,7 @@ mod sync_context;
|
||||
mod sync_notes;
|
||||
mod sync_profiles;
|
||||
mod sync_reading_list;
|
||||
mod sync_site_permissions;
|
||||
mod tab_group_order;
|
||||
mod tab_groups;
|
||||
mod tab_lifecycle;
|
||||
|
||||
@@ -28,6 +28,9 @@ impl BrowserCore {
|
||||
for record in body.reading_list {
|
||||
self.apply_reading_list_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
for record in body.site_permissions {
|
||||
self.apply_site_permission_sync_record(record, &mut summary, &context)?;
|
||||
}
|
||||
Ok(summary)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
use ely_domain::{
|
||||
SiteOrigin, SitePermissionDecision, SitePermissionEntry, SitePermissionFeature, SyncObjectKind,
|
||||
SyncObjectPolicy,
|
||||
};
|
||||
use ely_sync_client::SyncClientError;
|
||||
|
||||
use super::{BrowserCore, sync::snapshot_schema_error, sync_context::SyncSnapshotApplyContext};
|
||||
use crate::{sync_engine::SyncSnapshotApplySummary, sync_records::SitePermissionSyncRecord};
|
||||
|
||||
impl BrowserCore {
|
||||
pub(crate) fn visible_site_permissions_for_sync(&self) -> Vec<&SitePermissionEntry> {
|
||||
if self.sync_object_policy(SyncObjectKind::SitePermissions) == SyncObjectPolicy::Paused {
|
||||
return Vec::new();
|
||||
}
|
||||
self.site_permissions
|
||||
.iter()
|
||||
.filter(|entry| self.profile_allows_cloud_sync(entry.profile_id()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(super) fn apply_site_permission_sync_record(
|
||||
&mut self,
|
||||
record: SitePermissionSyncRecord,
|
||||
summary: &mut SyncSnapshotApplySummary,
|
||||
context: &SyncSnapshotApplyContext,
|
||||
) -> Result<(), SyncClientError> {
|
||||
let profile_id = self.sync_profile_id(&record.profile_id, context)?;
|
||||
let origin = SiteOrigin::parse(record.origin).map_err(snapshot_schema_error)?;
|
||||
let feature =
|
||||
SitePermissionFeature::parse(&record.feature).map_err(snapshot_schema_error)?;
|
||||
let decision =
|
||||
SitePermissionDecision::parse(&record.decision).map_err(snapshot_schema_error)?;
|
||||
let existing_index = self.site_permissions.iter().position(|entry| {
|
||||
entry.profile_id() == &profile_id
|
||||
&& entry.origin() == &origin
|
||||
&& entry.feature() == feature
|
||||
});
|
||||
let entry = SitePermissionEntry::new(profile_id, origin, feature, decision);
|
||||
|
||||
match existing_index {
|
||||
Some(index) if self.site_permissions[index] == entry => summary.record_skipped(),
|
||||
Some(index) => {
|
||||
self.site_permissions[index] = entry;
|
||||
summary.record_updated();
|
||||
}
|
||||
None => {
|
||||
self.site_permissions.push(entry);
|
||||
summary.record_imported();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use ely_domain::{
|
||||
ArchivePolicy, BookmarkEntry, BrowserTab, NoteEntry, NoteTarget, Profile, ProfileKind,
|
||||
ProfileSyncPolicy, ReadingListEntry, ReadingProgress, Space, TabFlags,
|
||||
ProfileSyncPolicy, ReadingListEntry, ReadingProgress, SitePermissionEntry, Space, TabFlags,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -24,6 +24,8 @@ pub(crate) struct SyncSnapshotBody {
|
||||
pub(crate) notes: Vec<NoteSyncRecord>,
|
||||
#[serde(default)]
|
||||
pub(crate) reading_list: Vec<ReadingListSyncRecord>,
|
||||
#[serde(default)]
|
||||
pub(crate) site_permissions: Vec<SitePermissionSyncRecord>,
|
||||
}
|
||||
|
||||
impl SyncSnapshotBody {
|
||||
@@ -74,6 +76,11 @@ impl SyncSnapshotBody {
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
site_permissions: core
|
||||
.visible_site_permissions_for_sync()
|
||||
.into_iter()
|
||||
.map(SitePermissionSyncRecord::from_entry)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,6 +380,25 @@ impl ReadingProgressSyncRecord {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct SitePermissionSyncRecord {
|
||||
pub(crate) profile_id: String,
|
||||
pub(crate) origin: String,
|
||||
pub(crate) feature: String,
|
||||
pub(crate) decision: String,
|
||||
}
|
||||
|
||||
impl SitePermissionSyncRecord {
|
||||
fn from_entry(entry: &SitePermissionEntry) -> Self {
|
||||
Self {
|
||||
profile_id: entry.profile_id().as_str().to_string(),
|
||||
origin: entry.origin().as_str().to_string(),
|
||||
feature: entry.feature().as_str().to_string(),
|
||||
decision: entry.decision().as_str().to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_sync_enabled() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{ProfileKind, ProfileSyncPolicy, UrlText};
|
||||
use ely_domain::{
|
||||
ProfileKind, ProfileSyncPolicy, SiteOrigin, SitePermissionDecision, SitePermissionFeature,
|
||||
UrlText,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_imports_remote_profiles_before_spaces() -> Result<(), Box<dyn Error>> {
|
||||
@@ -76,6 +79,11 @@ fn paused_profile_data_is_omitted_from_sync_snapshots() -> Result<(), Box<dyn Er
|
||||
source.bookmark_active_tab()?;
|
||||
source.save_active_url_note("profile paused")?;
|
||||
source.save_active_tab_to_reading_list()?;
|
||||
source.set_site_permission(
|
||||
SiteOrigin::parse("https://example.com")?,
|
||||
SitePermissionFeature::Camera,
|
||||
SitePermissionDecision::AllowAlways,
|
||||
)?;
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
@@ -92,5 +100,6 @@ fn paused_profile_data_is_omitted_from_sync_snapshots() -> Result<(), Box<dyn Er
|
||||
assert!(snapshot.bookmarks.is_empty());
|
||||
assert!(snapshot.notes.is_empty());
|
||||
assert!(snapshot.reading_list.is_empty());
|
||||
assert!(snapshot.site_permissions.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
use std::error::Error;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{
|
||||
SiteOrigin, SitePermissionDecision, SitePermissionFeature, SyncObjectKind, SyncObjectPolicy,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_imports_remote_site_permissions() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
let origin = SiteOrigin::parse("https://example.com")?;
|
||||
source.set_site_permission(
|
||||
origin.clone(),
|
||||
SitePermissionFeature::Camera,
|
||||
SitePermissionDecision::AllowAlways,
|
||||
)?;
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let target_profile_id = target.snapshot()?.active_profile_id;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let [entry] = snapshot.site_permissions.as_slice() else {
|
||||
return Err(
|
||||
format!("expected 1 site permission, got {}", snapshot.site_permissions.len()).into()
|
||||
);
|
||||
};
|
||||
|
||||
assert_eq!(summary.imported(), 1);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert_eq!(entry.profile_id(), &target_profile_id);
|
||||
assert_eq!(entry.origin(), &origin);
|
||||
assert_eq!(entry.feature(), SitePermissionFeature::Camera);
|
||||
assert_eq!(entry.decision(), SitePermissionDecision::AllowAlways);
|
||||
assert!(snapshot.site_permission_audit_events.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_updates_existing_site_permission() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
let origin = SiteOrigin::parse("https://example.com")?;
|
||||
source.set_site_permission(
|
||||
origin.clone(),
|
||||
SitePermissionFeature::Notifications,
|
||||
SitePermissionDecision::AllowAlways,
|
||||
)?;
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
target.set_site_permission(
|
||||
origin,
|
||||
SitePermissionFeature::Notifications,
|
||||
SitePermissionDecision::DenyAlways,
|
||||
)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
let [entry] = snapshot.site_permissions.as_slice() else {
|
||||
return Err(
|
||||
format!("expected 1 site permission, got {}", snapshot.site_permissions.len()).into()
|
||||
);
|
||||
};
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 1);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert_eq!(entry.feature(), SitePermissionFeature::Notifications);
|
||||
assert_eq!(entry.decision(), SitePermissionDecision::AllowAlways);
|
||||
assert_eq!(snapshot.site_permission_audit_events.len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_snapshot_omits_paused_site_permissions() -> Result<(), Box<dyn Error>> {
|
||||
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let source_home_tab_id = source.snapshot()?.active_tab_id;
|
||||
source.set_tab_sync_enabled(&source_home_tab_id, false)?;
|
||||
source.set_site_permission(
|
||||
SiteOrigin::parse("https://example.com")?,
|
||||
SitePermissionFeature::Popups,
|
||||
SitePermissionDecision::DenyAlways,
|
||||
)?;
|
||||
source.set_sync_object_policy(SyncObjectKind::SitePermissions, SyncObjectPolicy::Paused);
|
||||
let bytes = source.build_sync_snapshot_bytes()?;
|
||||
|
||||
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let summary = target.apply_sync_snapshot_bytes(&bytes)?;
|
||||
let snapshot = target.snapshot()?;
|
||||
|
||||
assert_eq!(summary.imported(), 0);
|
||||
assert_eq!(summary.updated(), 0);
|
||||
assert_eq!(summary.skipped(), 0);
|
||||
assert!(snapshot.site_permissions.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user