From 62112b99a9e59bd5b7de81e2987bb1568430f38d Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 13 Nov 2024 15:54:35 +0100 Subject: [PATCH] [PM-9022] scaffold the extension and build pipeline (#9948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add macos xcode project * feat: add extension to mas build * feat: use `after-sign` to avoid issues Electron builder modifies the .plist in the extension which causes issues with the signing process. Copying and re-signing manually avoids this because it bypasses the electron builder for the extension * feat: always clean build and add better error handling * chore: add some logging to after-sign * feat: automatically cleanup xcode build to avoid duplicate extensions * docs: add information about managing extensions * feat: add missing safari extension logging * lint: allow macos filenames * chore: add macos to platform ownership * lint: add some additional allowed files * feat: don't build autofill extension for MAS * chore: ignore capital letters linting for all macos files * chore: replace gulpfile with regular node script * chore: add lint rules to script * lint: fix remaining lint issues in script * chore: tweak lint rule * feat: remove desktop target * fix: use new provisioning profile for dev extension * Update to unblock CI builds * chore: remove extension from masdev pack This way we don't include the extension in any build and can avoid the signing issues it brings * chore: add autofill as codeowner * chore: remove xcuserdata * chore: ignore xcuserdata --------- Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com> Co-authored-by: Michał Chęciński Co-authored-by: Matt Bishop --- .github/CODEOWNERS | 2 + .github/workflows/build-desktop.yml | 20 +- .github/workflows/lint.yml | 1 + apps/desktop/.gitignore | 1 + apps/desktop/macos/README.md | 23 ++ .../CredentialProviderViewController.xib | 69 ++++ .../CredentialProviderViewController.swift | 93 +++++ .../macos/autofill-extension/Info.plist | 23 ++ .../autofill_extension.entitlements | 10 + .../macos/desktop.xcodeproj/project.pbxproj | 367 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + apps/desktop/macos/production.xcconfig | 11 + apps/desktop/package.json | 2 + apps/desktop/resources/entitlements.mac.plist | 2 + .../resources/entitlements.mas.inherit.plist | 2 + apps/desktop/resources/entitlements.mas.plist | 2 + apps/desktop/scripts/after-sign.js | 68 +++- apps/desktop/scripts/build-macos-extension.js | 62 +++ 18 files changed, 740 insertions(+), 26 deletions(-) create mode 100644 apps/desktop/macos/README.md create mode 100644 apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib create mode 100644 apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift create mode 100644 apps/desktop/macos/autofill-extension/Info.plist create mode 100644 apps/desktop/macos/autofill-extension/autofill_extension.entitlements create mode 100644 apps/desktop/macos/desktop.xcodeproj/project.pbxproj create mode 100644 apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 apps/desktop/macos/production.xcconfig create mode 100644 apps/desktop/scripts/build-macos-extension.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93693f183c30..c050ee1f6c0a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -71,6 +71,7 @@ bitwarden_license/bit-web/src/app/billing @bitwarden/team-billing-dev ## Platform team files ## apps/browser/src/platform @bitwarden/team-platform-dev apps/cli/src/platform @bitwarden/team-platform-dev +apps/desktop/macos @bitwarden/team-platform-dev apps/desktop/src/platform @bitwarden/team-platform-dev apps/web/src/app/platform @bitwarden/team-platform-dev libs/angular/src/platform @bitwarden/team-platform-dev @@ -91,6 +92,7 @@ apps/web/src/translation-constants.ts @bitwarden/team-platform-dev apps/browser/src/autofill @bitwarden/team-autofill-dev apps/desktop/src/autofill @bitwarden/team-autofill-dev libs/common/src/autofill @bitwarden/team-autofill-dev +apps/desktop/macos/autofill-extension @bitwarden/team-autofill-dev # DuckDuckGo integration apps/desktop/native-messaging-test-runner @bitwarden/team-autofill-dev apps/desktop/src/services/native-message-handler.service.ts @bitwarden/team-autofill-dev diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index 221c998247f3..bb2889983b41 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -1164,6 +1164,21 @@ jobs: --file $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ --output none + az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \ + --name bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --file $HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile \ + --output none + + - name: Set up provisioning profiles + run: | + AUTOFILL_PROFILE_PATH=$HOME/secrets/bitwarden_desktop_autofill_app_store_2024.provisionprofile + PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles + + mkdir -p "$PROFILES_DIR_PATH" + + AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}") + cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.provisionprofile" + - name: Get certificates run: | mkdir -p $HOME/certificates @@ -1215,11 +1230,6 @@ jobs: security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain - - name: Set up provisioning profiles - run: | - cp $HOME/secrets/bitwarden_desktop_appstore.provisionprofile \ - $GITHUB_WORKSPACE/apps/desktop/bitwarden_desktop_appstore.provisionprofile - - name: Increment version shell: pwsh env: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 561cd9af0c8f..9dc72c7fddaf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,6 +36,7 @@ jobs: ! -path "./.github/*" \ ! -path "*/Cargo.toml" \ ! -path "*/Cargo.lock" \ + ! -path "./apps/desktop/macos/*" \ > tmp.txt diff <(sort .github/whitelist-capital-letters.txt) <(sort tmp.txt) diff --git a/apps/desktop/.gitignore b/apps/desktop/.gitignore index 040b2179fab2..444c9a85100a 100644 --- a/apps/desktop/.gitignore +++ b/apps/desktop/.gitignore @@ -2,3 +2,4 @@ dist-safari/ *.nupkg *.env PlugIns/safari.appex/ +xcuserdata/ diff --git a/apps/desktop/macos/README.md b/apps/desktop/macos/README.md new file mode 100644 index 000000000000..6e016144b4bc --- /dev/null +++ b/apps/desktop/macos/README.md @@ -0,0 +1,23 @@ +# MacOS Extensions for Desktop Apps + +This folder contains an Xcode project that builds macOS extensions for our desktop app. The extensions are used to provide additional functionality to the desktop app, such as autofill (password and passkeys). + +## Manage loaded extensions + +macOS automatically loads extensions from apps, even if they have never been used (especially if built with Xcode). This can be confusing when you have multiple copies of the same application. To see where an extension is loaded from, use the following command: + +```bash +# To list all extensions +pluginkit -m -v + +# To list a specific extension +pluginkit -m -v -i com.bitwarden.desktop.autofill-extension +``` + +To unregister an extension, you can either remove it from your filesystem, or use the following command: + +```bash +pluginkit -r +``` + +where the path to the .appex file can be found in the output of the first command. diff --git a/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib new file mode 100644 index 000000000000..ace3497a58bb --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift new file mode 100644 index 000000000000..d5c5cabeee48 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -0,0 +1,93 @@ +// +// CredentialProviderViewController.swift +// autofill-extension +// +// Created by Andreas Coroiu on 2023-12-21. +// + +import AuthenticationServices +import os + +class CredentialProviderViewController: ASCredentialProviderViewController { + let logger = Logger() + + /* + Implement this method if your extension supports showing credentials in the QuickType bar. + When the user selects a credential from your app, this method will be called with the + ASPasswordCredentialIdentity your app has previously saved to the ASCredentialIdentityStore. + Provide the password by completing the extension request with the associated ASPasswordCredential. + If using the credential would require showing custom UI for authenticating the user, cancel + the request with error code ASExtensionError.userInteractionRequired. + + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { + let databaseIsUnlocked = true + if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } else { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) + } + } + */ + + /* + Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with + ASExtensionError.userInteractionRequired. In this case, the system may present your extension's + UI and call this method. Show appropriate UI for authenticating the user then provide the password + by completing the extension request with the associated ASPasswordCredential. + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + } + */ + + @IBAction func cancel(_ sender: AnyObject?) { + self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code: ASExtensionError.userCanceled.rawValue)) + } + + @IBAction func passwordSelected(_ sender: AnyObject?) { + let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) + } + + override func prepareInterfaceForExtensionConfiguration() { + logger.log("[autofill-extension] prepareInterfaceForExtensionConfiguration called") + } + + override func prepareInterface(forPasskeyRegistration registrationRequest: ASCredentialRequest) { + logger.log("[autofill-extension] prepare interface for registration request \(registrationRequest.description)") + +// self.extensionContext.cancelRequest(withError: ExampleError.nope) + } + + override func prepareInterfaceToProvideCredential(for credentialRequest: ASCredentialRequest) { + logger.log("[autofill-extension] prepare interface for credential request \(credentialRequest.description)") + } + + /* + Prepare your UI to list available credentials for the user to choose from. The items in + 'serviceIdentifiers' describe the service the user is logging in to, so your extension can + prioritize the most relevant credentials in the list. + */ + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) { + logger.log("[autofill-extension] prepareCredentialList for serviceIdentifiers: \(serviceIdentifiers.count)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + } + + override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) { + logger.log("[autofill-extension] prepareInterfaceToProvideCredential for credentialIdentity: \(credentialIdentity.user)") + } + + override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier], requestParameters: ASPasskeyCredentialRequestParameters) { + logger.log("[autofill-extension] prepareCredentialList(passkey) for serviceIdentifiers: \(serviceIdentifiers.count)") + + for serviceIdentifier in serviceIdentifiers { + logger.log(" service: \(serviceIdentifier.identifier)") + } + + logger.log("request parameters: \(requestParameters.relyingPartyIdentifier)") + } + +} diff --git a/apps/desktop/macos/autofill-extension/Info.plist b/apps/desktop/macos/autofill-extension/Info.plist new file mode 100644 index 000000000000..539cfa35b9dd --- /dev/null +++ b/apps/desktop/macos/autofill-extension/Info.plist @@ -0,0 +1,23 @@ + + + + + NSExtension + + NSExtensionAttributes + + ASCredentialProviderExtensionCapabilities + + ProvidesPasskeys + + + ASCredentialProviderExtensionShowsConfigurationUI + + + NSExtensionPointIdentifier + com.apple.authentication-services-credential-provider-ui + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).CredentialProviderViewController + + + diff --git a/apps/desktop/macos/autofill-extension/autofill_extension.entitlements b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements new file mode 100644 index 000000000000..2e600a8d5299 --- /dev/null +++ b/apps/desktop/macos/autofill-extension/autofill_extension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.security.app-sandbox + + + diff --git a/apps/desktop/macos/desktop.xcodeproj/project.pbxproj b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..313b158895c9 --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.pbxproj @@ -0,0 +1,367 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */; }; + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */; }; + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Production.xcconfig; sourceTree = ""; }; + E1DF713C2B342F6900F29026 /* autofill-extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "autofill-extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; + E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; + E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialProviderViewController.swift; sourceTree = ""; }; + E1DF71442B342F6900F29026 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CredentialProviderViewController.xib; sourceTree = ""; }; + E1DF71462B342F6900F29026 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = autofill_extension.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E1DF71392B342F6900F29026 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF713F2B342F6900F29026 /* AuthenticationServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E1DF711D2B342E2800F29026 = { + isa = PBXGroup; + children = ( + 968ED08A2C52A47200FFFEE6 /* Production.xcconfig */, + E1DF71402B342F6900F29026 /* autofill-extension */, + E1DF713D2B342F6900F29026 /* Frameworks */, + E1DF71272B342E2800F29026 /* Products */, + ); + sourceTree = ""; + }; + E1DF71272B342E2800F29026 /* Products */ = { + isa = PBXGroup; + children = ( + E1DF713C2B342F6900F29026 /* autofill-extension.appex */, + ); + name = Products; + sourceTree = ""; + }; + E1DF713D2B342F6900F29026 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E1DF713E2B342F6900F29026 /* AuthenticationServices.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + E1DF71402B342F6900F29026 /* autofill-extension */ = { + isa = PBXGroup; + children = ( + E1DF71412B342F6900F29026 /* CredentialProviderViewController.swift */, + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */, + E1DF71462B342F6900F29026 /* Info.plist */, + E1DF71472B342F6900F29026 /* autofill_extension.entitlements */, + ); + path = "autofill-extension"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E1DF713B2B342F6900F29026 /* autofill-extension */ = { + isa = PBXNativeTarget; + buildConfigurationList = E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */; + buildPhases = ( + E1DF71382B342F6900F29026 /* Sources */, + E1DF71392B342F6900F29026 /* Frameworks */, + E1DF713A2B342F6900F29026 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "autofill-extension"; + productName = "autofill-extension"; + productReference = E1DF713C2B342F6900F29026 /* autofill-extension.appex */; + productType = "com.apple.product-type.app-extension"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E1DF711E2B342E2800F29026 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1510; + LastUpgradeCheck = 1510; + TargetAttributes = { + E1DF713B2B342F6900F29026 = { + CreatedOnToolsVersion = 15.1; + }; + }; + }; + buildConfigurationList = E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E1DF711D2B342E2800F29026; + productRefGroup = E1DF71272B342E2800F29026 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E1DF713B2B342F6900F29026 /* autofill-extension */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E1DF713A2B342F6900F29026 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF71452B342F6900F29026 /* CredentialProviderViewController.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E1DF71382B342F6900F29026 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E1DF71422B342F6900F29026 /* CredentialProviderViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E1DF71432B342F6900F29026 /* CredentialProviderViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + E1DF71442B342F6900F29026 /* Base */, + ); + name = CredentialProviderViewController.xib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E1DF71332B342E2900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E1DF71342B342E2900F29026 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 14.2; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + E1DF714C2B342F6900F29026 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "autofill-extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Bitwarden; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + E1DF714D2B342F6900F29026 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_ENTITLEMENTS = "autofill-extension/autofill_extension.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + CODE_SIGN_STYLE = Manual; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=macosx*]" = LTZ2PFU5D6; + ENABLE_HARDENED_RUNTIME = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "autofill-extension/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = Bitwarden; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.bitwarden.desktop.autofill-extension"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "Bitwarden Desktop Autofill Development 2024"; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E1DF71212B342E2800F29026 /* Build configuration list for PBXProject "desktop" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF71332B342E2900F29026 /* Debug */, + E1DF71342B342E2900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E1DF714E2B342F6900F29026 /* Build configuration list for PBXNativeTarget "autofill-extension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E1DF714C2B342F6900F29026 /* Debug */, + E1DF714D2B342F6900F29026 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E1DF711E2B342E2800F29026 /* Project object */; +} diff --git a/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/apps/desktop/macos/desktop.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/apps/desktop/macos/production.xcconfig b/apps/desktop/macos/production.xcconfig new file mode 100644 index 000000000000..f06f2bf736ef --- /dev/null +++ b/apps/desktop/macos/production.xcconfig @@ -0,0 +1,11 @@ +// +// Production.xcconfig +// desktop +// +// Created by Vince Grassia on 7/25/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 +CODE_SIGN_IDENTITY[sdk=macosx*] = 3rd Party Mac Developer Application +PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = Bitwarden Desktop Autofill App Store 2024 diff --git a/apps/desktop/package.json b/apps/desktop/package.json index c9e33b7110a5..423650cdcec0 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -23,6 +23,7 @@ "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", + "build:macos-extension": "node scripts/build-macos-extension.js", "build:main": "cross-env NODE_ENV=production webpack --config webpack.main.js", "build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js", "build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.main.js --watch", @@ -38,6 +39,7 @@ "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", "pack:mac:masdev": "npm run clean:dist && electron-builder --mac mas-dev --universal -p never", + "pack:mac:masdev:with-extension": "npm run clean:dist && npm run build:macos-extension && electron-builder --mac mas-dev --universal -p never", "pack:win": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never -c.win.certificateSubjectName=\"8bit Solutions LLC\"", "pack:win:ci": "npm run clean:dist && electron-builder --win --x64 --arm64 --ia32 -p never", "dist:dir": "npm run build && npm run pack:dir", diff --git a/apps/desktop/resources/entitlements.mac.plist b/apps/desktop/resources/entitlements.mac.plist index 48f7bf5cece0..915232b83f6d 100644 --- a/apps/desktop/resources/entitlements.mac.plist +++ b/apps/desktop/resources/entitlements.mac.plist @@ -8,5 +8,7 @@ com.apple.security.cs.disable-library-validation + com.apple.developer.authentication-services.autofill-credential-provider + diff --git a/apps/desktop/resources/entitlements.mas.inherit.plist b/apps/desktop/resources/entitlements.mas.inherit.plist index 3ee76423e4c6..3634c84f81af 100644 --- a/apps/desktop/resources/entitlements.mas.inherit.plist +++ b/apps/desktop/resources/entitlements.mas.inherit.plist @@ -10,5 +10,7 @@ com.apple.security.cs.disable-library-validation + com.apple.developer.authentication-services.autofill-credential-provider + diff --git a/apps/desktop/resources/entitlements.mas.plist b/apps/desktop/resources/entitlements.mas.plist index d42ade962c32..9ab2d3824a8d 100644 --- a/apps/desktop/resources/entitlements.mas.plist +++ b/apps/desktop/resources/entitlements.mas.plist @@ -16,6 +16,8 @@ com.apple.security.files.user-selected.read-write + com.apple.developer.authentication-services.autofill-credential-provider + com.apple.security.temporary-exception.files.home-relative-path.read-write /Library/Application Support/Mozilla/NativeMessagingHosts/ diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index 69c078a13b5e..dc60e9d18385 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -15,36 +15,62 @@ async function run(context) { const appName = context.packager.appInfo.productFilename; const appPath = `${context.appOutDir}/${appName}.app`; const macBuild = context.electronPlatformName === "darwin"; - const copyPlugIn = ["darwin", "mas"].includes(context.electronPlatformName); + const copySafariExtension = ["darwin", "mas"].includes(context.electronPlatformName); + const copyAutofillExtension = ["mas"].includes(context.electronPlatformName); - if (copyPlugIn) { + let shouldResign = false; + + // cannot use extraFiles because it modifies the extensions .plist and makes it invalid + if (copyAutofillExtension) { + console.log("### Copying autofill extension"); + const extensionPath = path.join(__dirname, "../macos/dist/autofill-extension.appex"); + if (!fse.existsSync(extensionPath)) { + console.log("### Autofill extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } + fse.copySync(extensionPath, path.join(appPath, "Contents/PlugIns/autofill-extension.appex")); + shouldResign = true; + } + } + + if (copySafariExtension) { + console.log("### Copying safari extension"); // Copy Safari plugin to work-around https://github.com/electron-userland/electron-builder/issues/5552 const plugIn = path.join(__dirname, "../PlugIns"); - if (fse.existsSync(plugIn)) { - fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + if (!fse.existsSync(plugIn)) { + console.log("### Safari extension not found - skipping"); + } else { + if (!fse.existsSync(path.join(appPath, "Contents/PlugIns"))) { + fse.mkdirSync(path.join(appPath, "Contents/PlugIns")); + } fse.copySync( path.join(plugIn, "safari.appex"), path.join(appPath, "Contents/PlugIns/safari.appex"), ); + shouldResign = true; + } + } - // Resign to sign safari extension - if (context.electronPlatformName === "mas") { - const masBuildOptions = deepAssign( - {}, - context.packager.platformSpecificBuildOptions, - context.packager.config.mas, - ); - if (context.targets.some((e) => e.name === "mas-dev")) { - deepAssign(masBuildOptions, { - type: "development", - }); - } - if (context.packager.packagerOptions.prepackaged == null) { - await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); - } - } else { - await context.packager.signApp(context, true); + if (shouldResign) { + // Resign to sign safari extension + if (context.electronPlatformName === "mas") { + const masBuildOptions = deepAssign( + {}, + context.packager.platformSpecificBuildOptions, + context.packager.config.mas, + ); + if (context.targets.some((e) => e.name === "mas-dev")) { + deepAssign(masBuildOptions, { + type: "development", + }); + } + if (context.packager.packagerOptions.prepackaged == null) { + await context.packager.sign(appPath, context.appOutDir, masBuildOptions, context.arch); } + } else { + await context.packager.signApp(context, true); } } diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js new file mode 100644 index 000000000000..3aa43fb67855 --- /dev/null +++ b/apps/desktop/scripts/build-macos-extension.js @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +const child = require("child_process"); +const { exit } = require("process"); + +const fse = require("fs-extra"); + +const paths = { + macosBuild: "./macos/build", + extensionBuild: "./macos/build/Release/autofill-extension.appex", + extensionDistDir: "./macos/dist", + extensionDist: "./macos/dist/autofill-extension.appex", + macOsProject: "./macos/desktop.xcodeproj", + macOsConfig: "./macos/production.xcconfig", +}; + +async function buildMacOs() { + if (fse.existsSync(paths.macosBuild)) { + fse.removeSync(paths.macosBuild); + } + + if (fse.existsSync(paths.extensionDistDir)) { + fse.removeSync(paths.extensionDistDir); + } + + const proc = child.spawn("xcodebuild", [ + "-project", + paths.macOsProject, + "-alltargets", + "-configuration", + "Release", + "-xcconfig", + paths.macOsConfig, + ]); + stdOutProc(proc); + await new Promise((resolve, reject) => + proc.on("close", (code) => { + if (code > 0) { + console.error("xcodebuild failed with code", code); + return reject(new Error(`xcodebuild failed with code ${code}`)); + } + console.log("xcodebuild success"); + resolve(); + }), + ); + + fse.mkdirSync(paths.extensionDistDir); + fse.copySync(paths.extensionBuild, paths.extensionDist); + // Delete the build dir, otherwise MacOS will load the extension from there instead of the Bitwarden.app bundle + fse.removeSync(paths.macosBuild); +} + +function stdOutProc(proc) { + proc.stdout.on("data", (data) => console.log(data.toString())); + proc.stderr.on("data", (data) => console.error(data.toString())); +} + +buildMacOs() + .then(() => console.log("macOS build complete")) + .catch((err) => { + console.error("macOS build failed", err); + exit(-1); + });