From ae4c1b29d2f856a4a9e3d6381c9c1ef868fcbcb3 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 7 Apr 2021 17:39:59 +0200 Subject: [PATCH 1/3] Add support for biometrics to Safari (#1775) * Add Biometrics support to Safari --- src/background/main.background.ts | 3 +- src/background/nativeMessaging.background.ts | 72 +++++++++--- src/safari/desktop.xcodeproj/project.pbxproj | 104 +++++++++--------- .../safari/SafariWebExtensionHandler.swift | 58 +++++++++- src/services/browserPlatformUtils.service.ts | 7 +- 5 files changed, 174 insertions(+), 70 deletions(-) diff --git a/src/background/main.background.ts b/src/background/main.background.ts index e2a1e462c9..628a8342e5 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -249,7 +249,8 @@ export default class MainBackground { this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService, this.environmentService, this.policyService, this.userService, this.messagingService); this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService, - this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService); + this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService, + this.platformUtilsService); this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService, this.platformUtilsService, this.analytics, this.vaultTimeoutService); diff --git a/src/background/nativeMessaging.background.ts b/src/background/nativeMessaging.background.ts index 26020d400d..56e3509f76 100644 --- a/src/background/nativeMessaging.background.ts +++ b/src/background/nativeMessaging.background.ts @@ -3,6 +3,7 @@ import { CryptoService } from 'jslib/abstractions/crypto.service'; import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; +import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; @@ -33,7 +34,8 @@ export class NativeMessagingBackground { constructor(private storageService: StorageService, private cryptoService: CryptoService, private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService, private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService, - private messagingService: MessagingService, private appIdService: AppIdService) { + private messagingService: MessagingService, private appIdService: AppIdService, + private platformUtilsService: PlatformUtilsService) { this.storageService.save(ConstantsService.biometricFingerprintValidated, false); if (chrome?.permissions?.onAdded) { @@ -48,17 +50,27 @@ export class NativeMessagingBackground { this.appId = await this.appIdService.getAppId(); this.storageService.save(ConstantsService.biometricFingerprintValidated, false); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.port = BrowserApi.connectNative('com.8bit.bitwarden'); this.connecting = true; + const connectedCallback = () => { + this.connected = true; + this.connecting = false; + resolve(); + }; + + // Safari has a bundled native component which is always available, no need to + // check if the desktop app is running. + if (this.platformUtilsService.isSafari()) { + connectedCallback(); + } + this.port.onMessage.addListener(async (message: any) => { switch (message.command) { case 'connected': - this.connected = true; - this.connecting = false; - resolve(); + connectedCallback(); break; case 'disconnected': if (this.connecting) { @@ -114,15 +126,10 @@ export class NativeMessagingBackground { break; } case 'wrongUserId': - this.messagingService.send('showDialog', { - text: this.i18nService.t('nativeMessagingWrongUserDesc'), - title: this.i18nService.t('nativeMessagingWrongUserTitle'), - confirmText: this.i18nService.t('ok'), - type: 'error', - }); + this.showWrongUserDialog(); default: // Ignore since it belongs to another device - if (message.appId !== this.appId) { + if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) { return; } @@ -154,19 +161,35 @@ export class NativeMessagingBackground { }); } + showWrongUserDialog() { + this.messagingService.send('showDialog', { + text: this.i18nService.t('nativeMessagingWrongUserDesc'), + title: this.i18nService.t('nativeMessagingWrongUserTitle'), + confirmText: this.i18nService.t('ok'), + type: 'error', + }); + } + async send(message: any) { if (!this.connected) { await this.connect(); } + if (this.platformUtilsService.isSafari()) { + this.postMessage(message); + } else { + this.postMessage({appId: this.appId, message: await this.encryptMessage(message)}); + } + } + + async encryptMessage(message: any) { if (this.sharedSecret == null) { await this.secureCommunication(); } message.timestamp = Date.now(); - const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); - this.postMessage({appId: this.appId, message: encrypted}); + return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret); } getResponse(): Promise { @@ -197,7 +220,10 @@ export class NativeMessagingBackground { } private async onMessage(rawMessage: any) { - const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); + let message = rawMessage; + if (!this.platformUtilsService.isSafari()) { + message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret)); + } if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) { // tslint:disable-next-line @@ -241,7 +267,21 @@ export class NativeMessagingBackground { } if (message.response === 'unlocked') { - this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); + await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)); + + // Verify key is correct by attempting to decrypt a secret + try { + await this.cryptoService.getFingerprint(await this.userService.getUserId()); + } catch (e) { + // tslint:disable-next-line + console.error('Unable to verify key:', e); + await this.cryptoService.clearKey(); + this.showWrongUserDialog(); + + message = false; + break; + } + this.vaultTimeoutService.biometricLocked = false; this.runtimeBackground.processMessage({command: 'unlocked'}, null, null); } diff --git a/src/safari/desktop.xcodeproj/project.pbxproj b/src/safari/desktop.xcodeproj/project.pbxproj index c3157a17bb..50c4059d07 100644 --- a/src/safari/desktop.xcodeproj/project.pbxproj +++ b/src/safari/desktop.xcodeproj/project.pbxproj @@ -31,7 +31,7 @@ containerPortal = 55E037402577FA6B00979016 /* Project object */; proxyType = 1; remoteGlobalIDString = 55E037592577FA6F00979016; - remoteInfo = "safari"; + remoteInfo = safari; }; /* End PBXContainerItemProxy section */ @@ -50,6 +50,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftAppKit.tbd; path = usr/lib/swift/libswiftAppKit.tbd; sourceTree = SDKROOT; }; 55E037482577FA6B00979016 /* desktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55E0374B2577FA6B00979016 /* desktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = desktop.entitlements; sourceTree = ""; }; 55E0374C2577FA6B00979016 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -127,6 +128,7 @@ 55E0375E2577FA6F00979016 /* Frameworks */ = { isa = PBXGroup; children = ( + 5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */, 55E0375F2577FA6F00979016 /* Cocoa.framework */, ); name = Frameworks; @@ -157,7 +159,7 @@ 55E037782577FA6F00979016 /* _locales */, ); name = Resources; - path = "safari"; + path = safari; sourceTree = SOURCE_ROOT; }; /* End PBXGroup section */ @@ -195,7 +197,7 @@ dependencies = ( ); name = safari; - productName = "safari"; + productName = safari; productReference = 55E0375A2577FA6F00979016 /* safari.appex */; productType = "com.apple.product-type.app-extension"; }; @@ -426,6 +428,53 @@ }; name = Release; }; + 55E037692577FA6F00979016 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_ENTITLEMENTS = safari/safari.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HGT9YVMPAL; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = safari/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 55E0376A2577FA6F00979016 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CODE_SIGN_ENTITLEMENTS = safari/safari.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = HGT9YVMPAL; + ENABLE_HARDENED_RUNTIME = YES; + INFOPLIST_FILE = safari/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; 55E0376D2577FA6F00979016 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -435,7 +484,7 @@ CODE_SIGN_ENTITLEMENTS = desktop/desktop.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = LTZ2PFU5D6; + DEVELOPMENT_TEAM = HGT9YVMPAL; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = desktop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -459,7 +508,7 @@ CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = LTZ2PFU5D6; + DEVELOPMENT_TEAM = HGT9YVMPAL; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = desktop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -473,51 +522,6 @@ }; name = Release; }; - 55E037692577FA6F00979016 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_ENTITLEMENTS = "safari/safari.entitlements"; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = LTZ2PFU5D6; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = "safari/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.14; - PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 55E0376A2577FA6F00979016 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_ENTITLEMENTS = "safari/safari.entitlements"; - CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = LTZ2PFU5D6; - ENABLE_HARDENED_RUNTIME = YES; - INFOPLIST_FILE = "safari/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@executable_path/../../../../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.14; - PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ diff --git a/src/safari/safari/SafariWebExtensionHandler.swift b/src/safari/safari/SafariWebExtensionHandler.swift index 35d7a498a2..fe241109e4 100644 --- a/src/safari/safari/SafariWebExtensionHandler.swift +++ b/src/safari/safari/SafariWebExtensionHandler.swift @@ -1,7 +1,9 @@ import SafariServices import os.log +import LocalAuthentication let SFExtensionMessageKey = "message" +let ServiceName = "Bitwarden" class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { @@ -78,14 +80,68 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { context.completeRequest(returningItems: [response], completionHandler: nil) } return + case "biometricUnlock": + + var error: NSError? + let laContext = LAContext() + + guard laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": "not supported", + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + ], + ], + ] + break; + } + laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Bitwarden Safari Extension") { (success, error) in + if success { + let passwordName = "key" + var passwordLength: UInt32 = 0 + var passwordPtr: UnsafeMutableRawPointer? = nil + + let status = SecKeychainFindGenericPassword(nil, UInt32(ServiceName.utf8.count), ServiceName, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil) + + if status == errSecSuccess { + let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String? + SecKeychainItemFreeContent(nil, passwordPtr) + + response.userInfo = [ SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": "unlocked", + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + "keyB64": result!.replacingOccurrences(of: "\"", with: ""), + ], + ]] + } else { + response.userInfo = [ + SFExtensionMessageKey: [ + "message": [ + "command": "biometricUnlock", + "response": "not enabled", + "timestamp": Int64(NSDate().timeIntervalSince1970 * 1000), + ], + ], + ] + } + } + + context.completeRequest(returningItems: [response], completionHandler: nil) + } + + return; default: return } context.completeRequest(returningItems: [response], completionHandler: nil) } - + } func jsonSerialize(obj: T?) -> String? { diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index b29aaff394..e93e50fc1e 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -289,8 +289,11 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService } async supportsBiometric() { - const isUnsuportedFirefox = this.isFirefox() && parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) < 87; - return !isUnsuportedFirefox && !this.isSafari(); + if (this.isFirefox()) { + return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87; + } + + return true; } authenticateBiometric() { From 97ac4a3267faedaf61f206b5ce9d475401d16b86 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 7 Apr 2021 20:43:07 +0200 Subject: [PATCH 2/3] Bump jslib (#1776) --- jslib | 2 +- src/background/main.background.ts | 4 ++-- src/popup/services/popup-search.service.ts | 5 +++-- src/popup/services/services.module.ts | 5 +++-- src/services/browserPlatformUtils.service.ts | 8 ++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/jslib b/jslib index c395293e64..78d40d9f18 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit c395293e64d5e8e276ede7ed84649fadde60dcb0 +Subproject commit 78d40d9f18c23a185465d5fca238b258b2848193 diff --git a/src/background/main.background.ts b/src/background/main.background.ts index 628a8342e5..dd027cc5d9 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -126,7 +126,7 @@ export default class MainBackground { analytics: Analytics; popupUtilsService: PopupUtilsService; sendService: SendServiceAbstraction; - fileUploadService: FileUploadServiceAbstraction + fileUploadService: FileUploadServiceAbstraction; onUpdatedRan: boolean; onReplacedRan: boolean; @@ -190,7 +190,7 @@ export default class MainBackground { this.storageService, this.i18nService, this.cipherService); this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, this.i18nService); - this.searchService = new SearchService(this.cipherService, this.consoleLogService); + this.searchService = new SearchService(this.cipherService, this.consoleLogService, this.i18nService); this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService, this.storageService, this.i18nService, this.cryptoFunctionService); this.stateService = new StateService(); diff --git a/src/popup/services/popup-search.service.ts b/src/popup/services/popup-search.service.ts index 2bab18acee..a866a060a6 100644 --- a/src/popup/services/popup-search.service.ts +++ b/src/popup/services/popup-search.service.ts @@ -1,12 +1,13 @@ import { CipherService } from 'jslib/abstractions/cipher.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; import { ConsoleLogService } from 'jslib/services/consoleLog.service'; import { SearchService } from 'jslib/services/search.service'; export class PopupSearchService extends SearchService { constructor(private mainSearchService: SearchService, cipherService: CipherService, - consoleLogService: ConsoleLogService) { - super(cipherService, consoleLogService); + consoleLogService: ConsoleLogService, i18nService: I18nService) { + super(cipherService, consoleLogService, i18nService); } clearIndex() { diff --git a/src/popup/services/services.module.ts b/src/popup/services/services.module.ts index 44cad897f1..1c7a5cd413 100644 --- a/src/popup/services/services.module.ts +++ b/src/popup/services/services.module.ts @@ -68,7 +68,8 @@ function getBgService(service: string) { export const stateService = new StateService(); export const messagingService = new BrowserMessagingService(); export const searchService = new PopupSearchService(getBgService('searchService')(), - getBgService('cipherService')(), getBgService('consoleLogService')()); + getBgService('cipherService')(), getBgService('consoleLogService')(), + getBgService('i18nService')()); export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService, popupUtilsService: PopupUtilsService): Function { @@ -90,7 +91,7 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ let theme = await storageService.get(ConstantsService.themeKey); if (theme == null) { - theme = platformUtilsService.getDefaultSystemTheme(); + theme = await platformUtilsService.getDefaultSystemTheme(); platformUtilsService.onDefaultSystemThemeChange(sysTheme => { window.document.documentElement.classList.remove('theme_light', 'theme_dark'); diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index e93e50fc1e..b770f91147 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -122,8 +122,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService BrowserApi.downloadFile(win, blobData, blobOptions, fileName); } - getApplicationVersion(): string { - return BrowserApi.getApplicationVersion(); + getApplicationVersion(): Promise { + return Promise.resolve(BrowserApi.getApplicationVersion()); } supportsWebAuthn(win: Window): boolean { @@ -314,8 +314,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService return false; } - getDefaultSystemTheme() { - return this.prefersColorSchemeDark.matches ? 'dark' : 'light'; + getDefaultSystemTheme(): Promise<'light' | 'dark'> { + return Promise.resolve(this.prefersColorSchemeDark.matches ? 'dark' : 'light'); } onDefaultSystemThemeChange(callback: ((theme: 'light' | 'dark') => unknown)) { From 544c7c526df4fb3c86630868d864ef18352ecbe0 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 9 Apr 2021 00:45:42 +0200 Subject: [PATCH 3/3] Show file picker warning in chromium on Mac 11 (#1778) --- src/popup/send/send-add-edit.component.html | 2 +- src/popup/send/send-add-edit.component.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/popup/send/send-add-edit.component.html b/src/popup/send/send-add-edit.component.html index f2306d1e37..3886170ed4 100644 --- a/src/popup/send/send-add-edit.component.html +++ b/src/popup/send/send-add-edit.component.html @@ -25,7 +25,7 @@ -
{{'sendLinuxChromiumFileWarning' | i18n}}
+
{{'sendLinuxChromiumFileWarning' | i18n}}
{{'sendFirefoxFileWarning' | i18n}}
{{'sendSafariFileWarning' | i18n}}
diff --git a/src/popup/send/send-add-edit.component.ts b/src/popup/send/send-add-edit.component.ts index bfa0c51f93..a7bb579070 100644 --- a/src/popup/send/send-add-edit.component.ts +++ b/src/popup/send/send-add-edit.component.ts @@ -34,6 +34,7 @@ export class SendAddEditComponent extends BaseAddEditComponent { inPopout = false; inSidebar = false; isLinux = false; + isUnsupportedMac = false; constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService, userService: UserService, messagingService: MessagingService, policyService: PolicyService, @@ -45,14 +46,14 @@ export class SendAddEditComponent extends BaseAddEditComponent { } get showFileSelector(): boolean { - return !this.editMode && (!this.isFirefox && !this.isSafari && !this.isLinux) || + return !this.editMode && (!this.isFirefox && !this.isSafari && !this.isLinux && !this.isUnsupportedMac) || (this.isFirefox && (this.inSidebar || this.inPopout)) || (this.isSafari && this.inPopout) || - (this.isLinux && !this.isFirefox && (this.inSidebar || this.inPopout)); + ((this.isLinux || this.isUnsupportedMac) && !this.isFirefox && (this.inSidebar || this.inPopout)); } get showFilePopoutMessage(): boolean { - return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showLinuxChromiumFileWarning); + return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning); } get showFirefoxFileWarning(): boolean { @@ -63,9 +64,9 @@ export class SendAddEditComponent extends BaseAddEditComponent { return this.isSafari && !this.inPopout; } - // Only show this for Chromium based browsers in Linux - get showLinuxChromiumFileWarning(): boolean { - return this.isLinux && !this.isFirefox && !(this.inSidebar || this.inPopout); + // Only show this for Chromium based browsers in Linux and Mac > Big Sur + get showChromiumFileWarning(): boolean { + return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout); } popOutWindow() { @@ -78,6 +79,7 @@ export class SendAddEditComponent extends BaseAddEditComponent { this.inPopout = this.popupUtilsService.inPopout(window); this.inSidebar = this.popupUtilsService.inSidebar(window); this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1; + this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11'); const queryParamsSub = this.route.queryParams.subscribe(async params => { if (params.sendId) {