mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
respond to review comments:
• replace usage of compile time flag with boolean for conditinal logic • moved sandbox specific logic to contained module inside macos.rs • remove redundant browser array from objc code and pass the target browser path as arg
This commit is contained in:
@@ -51,25 +51,21 @@ pub enum LoginImportResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait InstalledBrowserRetriever {
|
pub trait InstalledBrowserRetriever {
|
||||||
fn get_installed_browsers() -> Result<Vec<String>>;
|
fn get_installed_browsers(mas_build: bool) -> Result<Vec<String>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DefaultInstalledBrowserRetriever {}
|
pub struct DefaultInstalledBrowserRetriever {}
|
||||||
|
|
||||||
impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever {
|
impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever {
|
||||||
fn get_installed_browsers() -> Result<Vec<String>> {
|
fn get_installed_browsers(mas_build: bool) -> Result<Vec<String>> {
|
||||||
let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len());
|
let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len());
|
||||||
|
|
||||||
#[allow(unused_variables)] // config only used outside of sandbox
|
#[allow(unused_variables)] // config only used outside of sandbox
|
||||||
for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
|
for (browser, config) in SUPPORTED_BROWSER_MAP.iter() {
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
if mas_build {
|
||||||
{
|
// show all browsers for MAS builds, user will grant access when selected
|
||||||
// macOS sandbox mode: show all browsers, user will grant access when selected
|
|
||||||
browsers.push((*browser).to_string());
|
browsers.push((*browser).to_string());
|
||||||
}
|
} else {
|
||||||
|
|
||||||
#[cfg(not(all(target_os = "macos", feature = "sandbox")))]
|
|
||||||
{
|
|
||||||
// When not in sandbox check file system directly
|
// When not in sandbox check file system directly
|
||||||
let data_dir = get_browser_data_dir(config)?;
|
let data_dir = get_browser_data_dir(config)?;
|
||||||
if data_dir.exists() {
|
if data_dir.exists() {
|
||||||
@@ -91,7 +87,7 @@ pub fn get_available_profiles(browser_name: &str) -> Result<Vec<ProfileInfo>> {
|
|||||||
/// This shows the permission dialog and creates a security-scoped bookmark,
|
/// This shows the permission dialog and creates a security-scoped bookmark,
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
||||||
pub fn request_browser_access(browser_name: &str) -> Result<()> {
|
pub fn request_browser_access(browser_name: &str) -> Result<()> {
|
||||||
platform::ScopedBrowserAccess::request_only(browser_name)?;
|
platform::sandbox::ScopedBrowserAccess::request_only(browser_name)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -99,7 +95,7 @@ pub fn request_browser_access(browser_name: &str) -> Result<()> {
|
|||||||
pub async fn import_logins(browser_name: &str, profile_id: &str) -> Result<Vec<LoginImportResult>> {
|
pub async fn import_logins(browser_name: &str, profile_id: &str) -> Result<Vec<LoginImportResult>> {
|
||||||
// In sandbox mode, resume access to browser directory (use the formerly created bookmark)
|
// In sandbox mode, resume access to browser directory (use the formerly created bookmark)
|
||||||
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
#[cfg(all(target_os = "macos", feature = "sandbox"))]
|
||||||
let _access = platform::ScopedBrowserAccess::resume(browser_name)?;
|
let _access = platform::sandbox::ScopedBrowserAccess::resume(browser_name)?;
|
||||||
|
|
||||||
let (data_dir, local_state) = load_local_state_for_browser(browser_name)?;
|
let (data_dir, local_state) = load_local_state_for_browser(browser_name)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
#[cfg(feature = "sandbox")]
|
|
||||||
use std::ffi::CString;
|
|
||||||
#[cfg(feature = "sandbox")]
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use security_framework::passwords::get_generic_password;
|
use security_framework::passwords::get_generic_password;
|
||||||
@@ -17,25 +12,37 @@ use crate::{
|
|||||||
//
|
//
|
||||||
|
|
||||||
#[cfg(feature = "sandbox")]
|
#[cfg(feature = "sandbox")]
|
||||||
|
pub mod sandbox {
|
||||||
|
use std::{ffi::CString, os::raw::c_char};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn requestBrowserAccess(browser_name: *const c_char) -> *mut c_char;
|
fn requestBrowserAccess(
|
||||||
|
browser_name: *const c_char,
|
||||||
|
relative_path: *const c_char,
|
||||||
|
) -> *mut c_char;
|
||||||
fn hasStoredBrowserAccess(browser_name: *const c_char) -> bool;
|
fn hasStoredBrowserAccess(browser_name: *const c_char) -> bool;
|
||||||
fn startBrowserAccess(browser_name: *const c_char) -> *mut c_char;
|
fn startBrowserAccess(browser_name: *const c_char) -> *mut c_char;
|
||||||
fn stopBrowserAccess(browser_name: *const c_char);
|
fn stopBrowserAccess(browser_name: *const c_char);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sandbox")]
|
|
||||||
pub struct ScopedBrowserAccess {
|
pub struct ScopedBrowserAccess {
|
||||||
browser_name: String,
|
browser_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sandbox")]
|
|
||||||
impl ScopedBrowserAccess {
|
impl ScopedBrowserAccess {
|
||||||
/// Request access to browser directory and create a security bookmark if access is approved
|
/// Request access to browser directory and create a security bookmark if access is approved
|
||||||
pub fn request_only(browser_name: &str) -> Result<()> {
|
pub fn request_only(browser_name: &str) -> Result<()> {
|
||||||
let c_name = CString::new(browser_name)?;
|
let config = crate::chromium::platform::SUPPORTED_BROWSERS
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.name == browser_name)
|
||||||
|
.ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?;
|
||||||
|
|
||||||
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr()) };
|
let c_name = CString::new(browser_name)?;
|
||||||
|
let c_path = CString::new(config.data_dir)?;
|
||||||
|
|
||||||
|
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr(), c_path.as_ptr()) };
|
||||||
if bookmark_ptr.is_null() {
|
if bookmark_ptr.is_null() {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"User denied access or selected an invalid browser directory"
|
"User denied access or selected an invalid browser directory"
|
||||||
@@ -68,7 +75,6 @@ impl ScopedBrowserAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "sandbox")]
|
|
||||||
impl Drop for ScopedBrowserAccess {
|
impl Drop for ScopedBrowserAccess {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let Ok(c_name) = CString::new(self.browser_name.as_str()) else {
|
let Ok(c_name) = CString::new(self.browser_name.as_str()) else {
|
||||||
@@ -77,6 +83,7 @@ impl Drop for ScopedBrowserAccess {
|
|||||||
unsafe { stopBrowserAccess(c_name.as_ptr()) };
|
unsafe { stopBrowserAccess(c_name.as_ptr()) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Public API
|
// Public API
|
||||||
|
|||||||
@@ -17,11 +17,12 @@ pub struct NativeImporterMetadata {
|
|||||||
/// Only browsers listed in PLATFORM_SUPPORTED_BROWSERS will have the "chromium" loader.
|
/// Only browsers listed in PLATFORM_SUPPORTED_BROWSERS will have the "chromium" loader.
|
||||||
/// All importers will have the "file" loader.
|
/// All importers will have the "file" loader.
|
||||||
pub fn get_supported_importers<T: InstalledBrowserRetriever>(
|
pub fn get_supported_importers<T: InstalledBrowserRetriever>(
|
||||||
|
mas_build: bool,
|
||||||
) -> HashMap<String, NativeImporterMetadata> {
|
) -> HashMap<String, NativeImporterMetadata> {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
// Check for installed browsers
|
// Check for installed browsers
|
||||||
let installed_browsers = T::get_installed_browsers().unwrap_or_default();
|
let installed_browsers = T::get_installed_browsers(mas_build).unwrap_or_default();
|
||||||
|
|
||||||
const IMPORTERS: &[(&str, &str)] = &[
|
const IMPORTERS: &[(&str, &str)] = &[
|
||||||
("chromecsv", "Chrome"),
|
("chromecsv", "Chrome"),
|
||||||
@@ -67,7 +68,7 @@ mod tests {
|
|||||||
pub struct MockInstalledBrowserRetriever {}
|
pub struct MockInstalledBrowserRetriever {}
|
||||||
|
|
||||||
impl InstalledBrowserRetriever for MockInstalledBrowserRetriever {
|
impl InstalledBrowserRetriever for MockInstalledBrowserRetriever {
|
||||||
fn get_installed_browsers() -> Result<Vec<String>, anyhow::Error> {
|
fn get_installed_browsers(_mas_build: bool) -> Result<Vec<String>, anyhow::Error> {
|
||||||
Ok(SUPPORTED_BROWSER_MAP
|
Ok(SUPPORTED_BROWSER_MAP
|
||||||
.keys()
|
.keys()
|
||||||
.map(|browser| browser.to_string())
|
.map(|browser| browser.to_string())
|
||||||
@@ -91,7 +92,7 @@ mod tests {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[test]
|
#[test]
|
||||||
fn macos_returns_all_known_importers() {
|
fn macos_returns_all_known_importers() {
|
||||||
let map = get_supported_importers::<MockInstalledBrowserRetriever>();
|
let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);
|
||||||
|
|
||||||
let expected: HashSet<String> = HashSet::from([
|
let expected: HashSet<String> = HashSet::from([
|
||||||
"chromecsv".to_string(),
|
"chromecsv".to_string(),
|
||||||
@@ -114,7 +115,7 @@ mod tests {
|
|||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[test]
|
#[test]
|
||||||
fn macos_specific_loaders_match_const_array() {
|
fn macos_specific_loaders_match_const_array() {
|
||||||
let map = get_supported_importers::<MockInstalledBrowserRetriever>();
|
let map = get_supported_importers::<MockInstalledBrowserRetriever>(false);
|
||||||
let ids = [
|
let ids = [
|
||||||
"chromecsv",
|
"chromecsv",
|
||||||
"chromiumcsv",
|
"chromiumcsv",
|
||||||
|
|||||||
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -253,7 +253,7 @@ export declare namespace chromium_importer {
|
|||||||
instructions: string
|
instructions: string
|
||||||
}
|
}
|
||||||
/** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */
|
/** Returns OS aware metadata describing supported Chromium based importers as a JSON string. */
|
||||||
export function getMetadata(): Record<string, NativeImporterMetadata>
|
export function getMetadata(masBuild: boolean): Record<string, NativeImporterMetadata>
|
||||||
export function getAvailableProfiles(browser: string): Array<ProfileInfo>
|
export function getAvailableProfiles(browser: string): Array<ProfileInfo>
|
||||||
export function importLogins(browser: string, profileId: string): Promise<Array<LoginImportResult>>
|
export function importLogins(browser: string, profileId: string): Promise<Array<LoginImportResult>>
|
||||||
export function requestBrowserAccess(browser: string): void
|
export function requestBrowserAccess(browser: string): void
|
||||||
|
|||||||
@@ -1166,8 +1166,10 @@ pub mod chromium_importer {
|
|||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
/// Returns OS aware metadata describing supported Chromium based importers as a JSON string.
|
/// Returns OS aware metadata describing supported Chromium based importers as a JSON string.
|
||||||
pub fn get_metadata() -> HashMap<String, NativeImporterMetadata> {
|
pub fn get_metadata(mas_build: bool) -> HashMap<String, NativeImporterMetadata> {
|
||||||
chromium_importer::metadata::get_supported_importers::<DefaultInstalledBrowserRetriever>()
|
chromium_importer::metadata::get_supported_importers::<DefaultInstalledBrowserRetriever>(
|
||||||
|
mas_build,
|
||||||
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata)))
|
.map(|(browser, metadata)| (browser, NativeImporterMetadata::from(metadata)))
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
// Request user permission to access browser directory
|
// Request user permission to access browser directory
|
||||||
// Returns base64-encoded bookmark data, or NULL if declined
|
// Returns base64-encoded bookmark data, or NULL if declined
|
||||||
// Caller must free returned string
|
// Caller must free returned string
|
||||||
char* requestBrowserAccess(const char* browserName);
|
char* requestBrowserAccess(const char* browserName, const char* relativePath);
|
||||||
|
|
||||||
// Check if we have stored bookmark (doesn't verify validity)
|
// Check if we have stored bookmark (doesn't verify validity)
|
||||||
bool hasStoredBrowserAccess(const char* browserName);
|
bool hasStoredBrowserAccess(const char* browserName);
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ static BrowserAccessManager* getManager() {
|
|||||||
return sharedManager;
|
return sharedManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* requestBrowserAccess(const char* browserName) {
|
char* requestBrowserAccess(const char* browserName, const char* relativePath) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
NSString* name = [NSString stringWithUTF8String:browserName];
|
NSString* name = [NSString stringWithUTF8String:browserName];
|
||||||
NSString* result = [getManager() requestAccessToBrowserDir:name];
|
NSString* path = [NSString stringWithUTF8String:relativePath];
|
||||||
|
NSString* result = [getManager() requestAccessToBrowserDir:name relativePath:path];
|
||||||
|
|
||||||
if (result == nil) {
|
if (result == nil) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
/// Request access to a specific browser's directory
|
/// Request access to a specific browser's directory
|
||||||
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
/// Returns security bookmark data (used to persist permissions) as base64 string, or nil if user declined
|
||||||
- (nullable NSString *)requestAccessToBrowserDir:(NSString *)browserName;
|
- (nullable NSString *)requestAccessToBrowserDir:(NSString *)browserName relativePath:(NSString *)relativePath;
|
||||||
|
|
||||||
/// Check if we have stored bookmark for browser (doesn't verify it's still valid)
|
/// Check if we have stored bookmark for browser (doesn't verify it's still valid)
|
||||||
- (BOOL)hasStoredAccess:(NSString *)browserName;
|
- (BOOL)hasStoredAccess:(NSString *)browserName;
|
||||||
|
|||||||
@@ -3,31 +3,19 @@
|
|||||||
|
|
||||||
@implementation BrowserAccessManager {
|
@implementation BrowserAccessManager {
|
||||||
NSString *_bookmarkKey;
|
NSString *_bookmarkKey;
|
||||||
NSDictionary<NSString *, NSString *> *_browserPaths;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self) {
|
if (self) {
|
||||||
_bookmarkKey = @"com.bitwarden.chromiumImporter.bookmarks";
|
_bookmarkKey = @"com.bitwarden.chromiumImporter.bookmarks";
|
||||||
|
|
||||||
_browserPaths = @{
|
|
||||||
@"Chrome": @"Library/Application Support/Google/Chrome",
|
|
||||||
@"Chromium": @"Library/Application Support/Chromium",
|
|
||||||
@"Microsoft Edge": @"Library/Application Support/Microsoft Edge",
|
|
||||||
@"Brave": @"Library/Application Support/BraveSoftware/Brave-Browser",
|
|
||||||
@"Arc": @"Library/Application Support/Arc/User Data",
|
|
||||||
@"Opera": @"Library/Application Support/com.operasoftware.Opera",
|
|
||||||
@"Vivaldi": @"Library/Application Support/Vivaldi"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString *)requestAccessToBrowserDir:(NSString *)browserName {
|
- (NSString *)requestAccessToBrowserDir:(NSString *)browserName relativePath:(NSString *)relativePath {
|
||||||
// NSLog(@"[OBJC] requestAccessToBrowserDir called for: %@", browserName);
|
// NSLog(@"[OBJC] requestAccessToBrowserDir called for: %@", browserName);
|
||||||
|
|
||||||
NSString *relativePath = _browserPaths[browserName];
|
|
||||||
if (!relativePath) {
|
if (!relativePath) {
|
||||||
// NSLog(@"[OBJC] Unknown browser: %@", browserName);
|
// NSLog(@"[OBJC] Unknown browser: %@", browserName);
|
||||||
return nil;
|
return nil;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { chromium_importer } from "@bitwarden/desktop-napi";
|
|||||||
|
|
||||||
export class ChromiumImporterService {
|
export class ChromiumImporterService {
|
||||||
constructor() {
|
constructor() {
|
||||||
ipcMain.handle("chromium_importer.getMetadata", async (event) => {
|
ipcMain.handle("chromium_importer.getMetadata", async (event, isMas: boolean) => {
|
||||||
return await chromium_importer.getMetadata();
|
return await chromium_importer.getMetadata(isMas);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Used on Mac OS App Store builds to request permissions to browser entries outside the sandbox
|
// Used on Mac OS App Store builds to request permissions to browser entries outside the sandbox
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export class DesktopImportMetadataService
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
const metadata = await ipc.tools.chromiumImporter.getMetadata();
|
const isMas = ipc.platform.isMacAppStore;
|
||||||
|
const metadata = await ipc.tools.chromiumImporter.getMetadata(isMas);
|
||||||
await this.parseNativeMetaData(metadata);
|
await this.parseNativeMetaData(metadata);
|
||||||
await super.init();
|
await super.init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { ipcRenderer } from "electron";
|
|||||||
import type { chromium_importer } from "@bitwarden/desktop-napi";
|
import type { chromium_importer } from "@bitwarden/desktop-napi";
|
||||||
|
|
||||||
const chromiumImporter = {
|
const chromiumImporter = {
|
||||||
getMetadata: (): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
|
getMetadata: (
|
||||||
ipcRenderer.invoke("chromium_importer.getMetadata"),
|
isMas: boolean,
|
||||||
|
): Promise<Record<string, chromium_importer.NativeImporterMetadata>> =>
|
||||||
|
ipcRenderer.invoke("chromium_importer.getMetadata", isMas),
|
||||||
// Request browser access for Mac OS App Store (sandboxed) builds (no-op in non-sandboxed builds)
|
// Request browser access for Mac OS App Store (sandboxed) builds (no-op in non-sandboxed builds)
|
||||||
requestBrowserAccess: (browser: string): Promise<void> =>
|
requestBrowserAccess: (browser: string): Promise<void> =>
|
||||||
ipcRenderer.invoke("chromium_importer.requestBrowserAccess", browser),
|
ipcRenderer.invoke("chromium_importer.requestBrowserAccess", browser),
|
||||||
|
|||||||
Reference in New Issue
Block a user