Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Data from Safari iOS #26914

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

Brandon-T
Copy link
Contributor

@Brandon-T Brandon-T commented Dec 6, 2024

Security Review

Summary

  • Add Password Importer API to Brave-Core and a Swift wrapper around it
  • Add History Importer API to Brave-Core and a Swift wrapper around it
  • Refactor Bookmarks Import API in Swift to be async-await

Resolves brave/brave-browser#42727

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

@Brandon-T Brandon-T added CI/skip-android Do not run CI builds for Android CI/skip-macos-x64 Do not run CI builds for macOS x64 CI/skip-windows-x64 Do not run CI builds for Windows x64 unused-CI/skip-linux-x64 Do not run CI builds for Linux x64 CI/skip-macos-arm64 Do not run CI builds for macOS arm64 CI/skip-teamcity Do not run builds in TeamCity labels Dec 6, 2024
@Brandon-T Brandon-T self-assigned this Dec 6, 2024
@Brandon-T Brandon-T requested a review from a team as a code owner December 6, 2024 16:26
Copy link
Contributor

github-actions bot commented Dec 6, 2024

The security team is monitoring all repositories for certain keywords. This PR includes the word(s) "password" and so security team members have been added as reviewers to take a look.

No need to request a full security review at this stage, the security team will take a look shortly and either clear the label or request more information/changes.

Notifications have already been sent, but if this is blocking your merge feel free to reach out directly to the security team on Slack so that we can expedite this check.

Copy link
Collaborator

@kylehickinson kylehickinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick glance feedback (haven't looked at implementation details yet):

  • Needs unit tests
  • Everything under ios/browser/api should be Obj-C wrappers, please move all of the actual importer logic to separate directory/directories. Try to match Chromium's directory structure in this regard
  • Is it possible to share this logic with desktop so that they can get Safari import via exported zip as well? (Perhaps with a layered component)

@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch from ea6722c to f1a9c7d Compare December 6, 2024 18:28
@Brandon-T Brandon-T requested a review from a team as a code owner December 6, 2024 18:28
}

size_t field_idx = 0;
CSVFieldParser parser(row);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that the parser here considers the grammar simple enough that it doesn't require using a memory safe language or separate process.

I believe this the solves for the rule of 2 issue here as well.

@kdenhartog
Copy link
Member

in general this seems ok to me, but I think it would be useful for @stoletheminerals to give this one a deeper look.

Copy link
Collaborator

@mkarolin mkarolin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not clear if we need a header in DEPS files, but for now ++

@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch from f1a9c7d to 67c2c7b Compare December 10, 2024 17:53
@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch 2 times, most recently from c21ba1c to da8e55e Compare December 18, 2024 16:59
@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch 2 times, most recently from 66c8b79 to c72538a Compare January 22, 2025 18:58
Copy link
Member

@thypon thypon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requires test

@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch from caddb70 to fa9e252 Compare February 19, 2025 16:42
@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch 2 times, most recently from b890b94 to 5c92a3a Compare February 20, 2025 19:55
Copy link
Collaborator

@kylehickinson kylehickinson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple more notes


public static let personalImportTitle = NSLocalizedString(
"personalImportTitle",
tableName: "DataImporter",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a table name for all these strings, since its in a separate target

Comment on lines 26 to 41
if kind == .passwordConflict {
Button {
dismiss()
} label: {
Label {
Text(Strings.close)
} icon: {
Image(braveSystemName: "leo.close")
.foregroundColor(Color(braveSystemName: .iconDefault))
.padding(8)
}
.labelStyle(.iconOnly)
}
.background(Color(braveSystemName: .materialSeparator), in: Circle())
.frame(maxWidth: .infinity, alignment: .trailing)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be part of the VStack, it should be in a overlay(alignment: .topTrailing)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also probably shouldn't be part of the ScrollView

Copy link
Contributor Author

@Brandon-T Brandon-T Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This view doesn't have a scroll-view and isn't embedded in one.

Comment on lines 38 to 27
@ObservedObject
private var model = DataImportModel()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a @StateObject if you're going to create it in the View

Comment on lines 23 to 33
@State
private var showSuccessView = false

@State
private var showFailureView = false

@State
private var showLoadingView = false

@State
private var showMainView = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per DM these should all be removed

Comment on lines 187 to 188
@ViewBuilder
private func mainView() -> some View {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be a func, can just be private var mainView: some View

Comment on lines 118 to 119
.sheet(
isPresented: $model.isLoadingProfiles,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These sheets are explicitly associated with the main view only it seems? Is that correct? If for instance the import state is any causing any of the other views to appear the sheets will be dismissed, should these go on the container view instead?

Comment on lines 130 to 135
.osAvailabilityModifiers({
if #available(iOS 16.4, *) {
} else {
$0.background(.thickMaterial)
}
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As Per DM you can't leave one condition empty, need to return the view provided as the closure is a ViewBuilder

@@ -0,0 +1,3 @@
include_rules = [
"+brave/components/password_manager/core/browser/import/safari_password_importer.h"
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOF newline missing

Comment on lines 51 to 52
if model.importState == .success {
DataImporterStateView(kind: .success, model: model) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's something weird here about creating an entire switch across import states all to display the same View (DataImporterStateView) with almost identical modifiers with the only thing changing is Buttons? You also seem to combine both making this reusable and a "View only" kind of component and also passing in the model which means you're now checking specific things related to the logic of the feature.

Either you should have one view and you only pass in the model and adjust inside directly based on importState, or you don't pass in the model and make sure you can pass in all necessary info from here.

You also probably want to find a way to do this without multiple if-else causing the creation of completely separate views for each state

Copy link
Contributor Author

@Brandon-T Brandon-T Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched them all to separate views instead of one shared view.

@kylehickinson
Copy link
Collaborator

image

This final page also doesnt match design

Comment on lines 115 to 117
Task.delayed(bySeconds: 1.0) { @MainActor in
self.importState = .dataConflict
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're already in an async method, just do try await Task.sleep(...) directly in it

Comment on lines 172 to 176
self.importState = .dataConflict
Task.delayed(bySeconds: 1.0) { @MainActor in
self.importState = .dataConflict
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

Copy link
Contributor

[puLL-Merge] - brave/brave-core@26914

Description

This PR adds support for importing Safari data into Brave on iOS. It includes functionality to import bookmarks, browsing history, and passwords from Safari export files, with careful handling of conflicts and data validation. The change also includes a new UI for guiding users through the import process.

Security Hotspots

  1. Password handling in safari_password_importer.cc and related files - Ensure passwords are properly encrypted in memory and securely cleared after use
  2. File handling in ZipImporter.swift - Check for zip bombs and malicious file paths
  3. CSV parsing in csv_safari_password.cc - Validate input to prevent buffer overflows or injection attacks
  4. IPC communication in csv_safari_password_parser_service.cc - Ensure proper sandboxing and validation of messages
Changes

Changes

By filename:

New Components:

  • components/password_manager/core/browser/import/: New Safari password import infrastructure
    • safari_password_importer.{h,cc}: Core importer implementation
    • csv_safari_password.{h,cc}: CSV file parsing for passwords
    • safari_import_results.{h,cc}: Results handling

iOS Changes:

  • ios/brave-ios/Sources/DataImporter/: New iOS UI for data import
    • Added import flow UI components
    • Multi-profile support
    • Error handling and success states
    • Tutorial for users

Build System:

  • Updated BUILD.gn files to support new components
  • Added dependencies for password importing
sequenceDiagram
    participant User
    participant UI
    participant Importer
    participant CSVParser
    participant Storage

    User->>UI: Initiates import
    UI->>Importer: Import request
    Importer->>CSVParser: Parse Safari export
    CSVParser-->>Importer: Parsed data
    
    alt Has Conflicts
        Importer->>UI: Show conflict resolution
        User->>UI: Resolve conflicts
        UI->>Importer: Resolution choice
    end

    Importer->>Storage: Store imported data
    Storage-->>UI: Import complete
    UI->>User: Show success/failure
Loading

@Brandon-T Brandon-T force-pushed the feature/HistoryBookmarksImportSafari branch from 987e4e5 to f05f0d1 Compare February 21, 2025 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/skip-android Do not run CI builds for Android CI/skip-macos-arm64 Do not run CI builds for macOS arm64 CI/skip-macos-x64 Do not run CI builds for macOS x64 CI/skip-teamcity Do not run builds in TeamCity CI/skip-windows-x64 Do not run CI builds for Windows x64 needs-security-review puLL-Merge unused-CI/skip-linux-x64 Do not run CI builds for Linux x64
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[iOS] - Export API for Importing Passwords and History from Safari
7 participants