Skip to content

Commit

Permalink
wip: experimenting with ios autofill provider extension
Browse files Browse the repository at this point in the history
  • Loading branch information
hpoul committed Feb 19, 2024
1 parent 713c58a commit 7a15662
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 5 deletions.
12 changes: 12 additions & 0 deletions authpass/ios/AuthPassAutofill/AuthPassAutofill.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?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.application-groups</key>
<array>
<string>group.design.codeux.authpass</string>
</array>
</dict>
</plist>
57 changes: 57 additions & 0 deletions authpass/ios/AuthPassAutofill/Base.lproj/MainInterface.storyboard
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14092" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Xki-Si-B7m">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14081.1"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Credential Provider View Controller-->
<scene sceneID="Uma-9u-xWV">
<objects>
<viewController id="Xki-Si-B7m" customClass="CredentialProviderViewController" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="BuU-Ak-iZz">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3wq-kG-lGu">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<items>
<navigationItem id="cbj-pk-SYj">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="bEZ-MG-jDy">
<connections>
<action selector="cancel:" destination="Xki-Si-B7m" id="6ap-3Q-iEX"/>
</connections>
</barButtonItem>
</navigationItem>
</items>
</navigationBar>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a7v-ug-QzG">
<rect key="frame" x="87.5" y="327" width="199" height="33"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<state key="normal" title="Return Example Password"/>
<connections>
<action selector="passwordSelected:" destination="Xki-Si-B7m" eventType="touchUpInside" id="ODd-lr-mud"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Ky8-vK-JVj" firstAttribute="top" secondItem="3wq-kG-lGu" secondAttribute="top" id="BIN-jb-uNd"/>
<constraint firstItem="3wq-kG-lGu" firstAttribute="width" secondItem="BuU-Ak-iZz" secondAttribute="width" id="UkD-v4-BcH"/>
<constraint firstItem="a7v-ug-QzG" firstAttribute="centerY" secondItem="Ky8-vK-JVj" secondAttribute="centerY" id="fAC-0v-NFE"/>
<constraint firstItem="a7v-ug-QzG" firstAttribute="centerX" secondItem="3wq-kG-lGu" secondAttribute="centerX" id="io1-ZS-gwn"/>
<constraint firstItem="3wq-kG-lGu" firstAttribute="centerX" secondItem="BuU-Ak-iZz" secondAttribute="centerX" id="rtV-5c-0bl"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Ky8-vK-JVj"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="RwB-HB-TSk" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
101 changes: 101 additions & 0 deletions authpass/ios/AuthPassAutofill/CredentialProviderViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//
// CredentialProviderViewController.swift
// AuthPassAutofill
//
// Created by Herbert Poul on 19.02.24.
// Copyright © 2024 The Chromium Authors. All rights reserved.
//

import AuthenticationServices
import Flutter


class CredentialProviderViewController: ASCredentialProviderViewController {

lazy var flutterEngine = FlutterEngine(name: "my flutter engine", project: nil, allowHeadlessExecution: false)

override func viewDidLoad() {
showFlutter()
}

func showFlutter() {
print("CredentialProviderViewController calling flutterEngine.run()")
flutterEngine.run(withEntrypoint: nil, initialRoute: "/autofill")
print("CredentialProviderViewController flutterEngine.run() finished.")
GeneratedPluginRegistrant.register(with: flutterEngine)
let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
flutterViewController.isViewOpaque = false
addChild(flutterViewController)
view.addSubview(flutterViewController.view)
flutterViewController.view.frame = view.bounds
self.view.layoutSubviews()
print("CredentialProviderViewController showing flutter view.. \(flutterViewController.isDisplayingFlutterUI)")
}

override func viewWillAppear(_ animated: Bool) {
print("CredentialProviderViewController viewWillAppear(\(animated)) -- \(flutterEngine.viewController?.isDisplayingFlutterUI)")
super.viewWillAppear(animated)
}

override func viewDidAppear(_ animated: Bool) {
print("CredentialProviderViewController viewDidAppear(\(animated)) \(flutterEngine.viewController?.isDisplayingFlutterUI)")
super.viewDidAppear(animated)
}

override func viewIsAppearing(_ animated: Bool) {
print("CredentialProviderViewController viewIsAppearing(\(animated)) \(flutterEngine.viewController?.isDisplayingFlutterUI)")
super.viewIsAppearing(animated)
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
print("CredentialProviderViewController viewWillTransition to: \(size)")
super.viewWillTransition(to: size, with: coordinator)
}

/*
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]) {
}

/*
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)
}

}
13 changes: 13 additions & 0 deletions authpass/ios/AuthPassAutofill/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?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>NSExtensionPrincipalClass</key>
<string>AuthPassAutofill.CredentialProviderViewController</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.authentication-services-credential-provider-ui</string>
</dict>
</dict>
</plist>
1 change: 1 addition & 0 deletions authpass/ios/AuthPassAutofill/Runner-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"
Loading

0 comments on commit 7a15662

Please sign in to comment.