-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PM-9022] scaffold the extension and build pipeline (#9948)
* 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 <[email protected]> Co-authored-by: Michał Chęciński <[email protected]> Co-authored-by: Matt Bishop <[email protected]>
- Loading branch information
1 parent
e341a66
commit 62112b9
Showing
18 changed files
with
740 additions
and
26 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ dist-safari/ | |
*.nupkg | ||
*.env | ||
PlugIns/safari.appex/ | ||
xcuserdata/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <path to .appex> | ||
``` | ||
|
||
where the path to the .appex file can be found in the output of the first command. |
69 changes: 69 additions & 0 deletions
69
apps/desktop/macos/autofill-extension/Base.lproj/CredentialProviderViewController.xib
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17021" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> | ||
<dependencies> | ||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17021"/> | ||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||
</dependencies> | ||
<objects> | ||
<customObject id="-2" userLabel="File's Owner" customClass="CredentialProviderViewController" customModuleProvider="target"> | ||
<connections> | ||
<outlet property="view" destination="1" id="2"/> | ||
</connections> | ||
</customObject> | ||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> | ||
<customObject id="-3" userLabel="Application" customClass="NSObject"/> | ||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1"> | ||
<rect key="frame" x="0.0" y="0.0" width="378" height="94"/> | ||
<subviews> | ||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1uM-r7-H1c"> | ||
<rect key="frame" x="177" y="3" width="197" height="32"/> | ||
<buttonCell key="cell" type="push" title="Return Example Password" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="2l4-PO-we5"> | ||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||
<font key="font" metaFont="system"/> | ||
<string key="keyEquivalent">D</string> | ||
<modifierMask key="keyEquivalentModifierMask" command="YES"/> | ||
</buttonCell> | ||
<connections> | ||
<action selector="passwordSelected:" target="-2" id="yic-EC-GGk"/> | ||
</connections> | ||
</button> | ||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz"> | ||
<rect key="frame" x="99" y="3" width="82" height="32"/> | ||
<constraints> | ||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/> | ||
</constraints> | ||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm"> | ||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> | ||
<font key="font" metaFont="system"/> | ||
<string key="keyEquivalent" base64-UTF8="YES"> | ||
Gw | ||
</string> | ||
</buttonCell> | ||
<connections> | ||
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/> | ||
</connections> | ||
</button> | ||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK"> | ||
<rect key="frame" x="135" y="63" width="108" height="16"/> | ||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="autofill-extension" id="0xp-rC-2gr"> | ||
<font key="font" metaFont="systemBold"/> | ||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> | ||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> | ||
</textFieldCell> | ||
</textField> | ||
</subviews> | ||
<constraints> | ||
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="1UO-J1-LbJ"/> | ||
<constraint firstItem="NVE-vN-dkz" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1" secondAttribute="leading" constant="20" symbolic="YES" id="3N9-qo-UfS"/> | ||
<constraint firstAttribute="bottom" secondItem="1uM-r7-H1c" secondAttribute="bottom" constant="10" id="4wH-De-nMF"/> | ||
<constraint firstItem="NVE-vN-dkz" firstAttribute="firstBaseline" secondItem="aNc-0i-CWK" secondAttribute="baseline" constant="50" id="Dpq-cK-cPE"/> | ||
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/> | ||
<constraint firstItem="1uM-r7-H1c" firstAttribute="leading" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="8" id="a8N-vS-Ew9"/> | ||
<constraint firstAttribute="trailing" secondItem="1uM-r7-H1c" secondAttribute="trailing" constant="10" id="qfT-cw-QQ2"/> | ||
<constraint firstAttribute="centerX" secondItem="aNc-0i-CWK" secondAttribute="centerX" id="uV3-Wn-RA3"/> | ||
<constraint firstItem="aNc-0i-CWK" firstAttribute="top" secondItem="1" secondAttribute="top" constant="15" id="vpR-tf-ebx"/> | ||
</constraints> | ||
<point key="canvasLocation" x="162" y="146"/> | ||
</customView> | ||
</objects> | ||
</document> |
93 changes: 93 additions & 0 deletions
93
apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)") | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>NSExtension</key> | ||
<dict> | ||
<key>NSExtensionAttributes</key> | ||
<dict> | ||
<key>ASCredentialProviderExtensionCapabilities</key> | ||
<dict> | ||
<key>ProvidesPasskeys</key> | ||
<true/> | ||
</dict> | ||
<key>ASCredentialProviderExtensionShowsConfigurationUI</key> | ||
<false/> | ||
</dict> | ||
<key>NSExtensionPointIdentifier</key> | ||
<string>com.apple.authentication-services-credential-provider-ui</string> | ||
<key>NSExtensionPrincipalClass</key> | ||
<string>$(PRODUCT_MODULE_NAME).CredentialProviderViewController</string> | ||
</dict> | ||
</dict> | ||
</plist> |
10 changes: 10 additions & 0 deletions
10
apps/desktop/macos/autofill-extension/autofill_extension.entitlements
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>com.apple.developer.authentication-services.autofill-credential-provider</key> | ||
<true/> | ||
<key>com.apple.security.app-sandbox</key> | ||
<true/> | ||
</dict> | ||
</plist> |
Oops, something went wrong.