diff --git a/Authenticator.xcodeproj/project.pbxproj b/Authenticator.xcodeproj/project.pbxproj index 29523f81..1c88730a 100644 --- a/Authenticator.xcodeproj/project.pbxproj +++ b/Authenticator.xcodeproj/project.pbxproj @@ -59,12 +59,36 @@ C9EB448E1C52A74200ACFA87 /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EB448D1C52A74200ACFA87 /* Component.swift */; }; C9EB44901C52AE4500ACFA87 /* AppController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EB448F1C52AE4500ACFA87 /* AppController.swift */; }; C9F7A8611C4D90B50082E5AE /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F7A8601C4D90B50082E5AE /* TokenStore.swift */; }; + CC00FE311FB66BD600A327C0 /* PasswordPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE301FB66BD600A327C0 /* PasswordPickerViewController.swift */; }; + CC00FE321FB66C1C00A327C0 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC46C4701DB3007D00EB4605 /* SearchField.swift */; }; + CC00FE371FB670D100A327C0 /* OpaqueNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE361FB670D100A327C0 /* OpaqueNavigationController.swift */; }; + CC00FE391FB6712400A327C0 /* ModelBasedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE381FB6712400A327C0 /* ModelBasedViewController.swift */; }; + CC00FE3A1FB6719200A327C0 /* OpaqueNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE361FB670D100A327C0 /* OpaqueNavigationController.swift */; }; + CC00FE3D1FB7884100A327C0 /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE3C1FB7884100A327C0 /* Root.swift */; }; + CC00FE3E1FB790E500A327C0 /* ModelBasedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE381FB6712400A327C0 /* ModelBasedViewController.swift */; }; + CC00FE401FB806BF00A327C0 /* ExtensionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC00FE3F1FB806BF00A327C0 /* ExtensionController.swift */; }; + CC00FE441FB8275300A327C0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC00FE431FB8275300A327C0 /* Images.xcassets */; }; CC46C4711DB3007D00EB4605 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC46C4701DB3007D00EB4605 /* SearchField.swift */; }; CC46C4731DB3040300EB4605 /* TokenListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC46C4721DB3040300EB4605 /* TokenListTests.swift */; }; CC471EEB1DC027A6006858AC /* TokenListViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC471EEA1DC027A6006858AC /* TokenListViewControllerTest.swift */; }; CC471EED1DC1377F006858AC /* MockTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC471EEC1DC1377F006858AC /* MockTableView.swift */; }; CCC409761DB28BFE000A050D /* TableDiffTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC409751DB28BFE000A050D /* TableDiffTests.swift */; }; CCCD668B1E1C74B4005FE96E /* OneTimePasswordExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCCD668A1E1C74B4005FE96E /* OneTimePasswordExtensions.swift */; }; + CCFF7C231FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = CCFF7C191FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + CCFF7C2B1FB4FF2E00FFBD89 /* Picker.js in Resources */ = {isa = PBXBuildFile; fileRef = CCFF7C2A1FB4FF2E00FFBD89 /* Picker.js */; }; + CCFF7C301FB50AC000FFBD89 /* OneTimePassword.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CCFF7C311FB50AC000FFBD89 /* OneTimePassword.framework */; }; + CCFF7C561FB50E3500FFBD89 /* TokenStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F7A8601C4D90B50082E5AE /* TokenStore.swift */; }; + CCFF7C581FB50EE100FFBD89 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCFF7C1B1FB4FCA100FFBD89 /* ActionViewController.swift */; }; + CCFF7C591FB5102500FFBD89 /* TokenList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C910ADC01BF0315A00C988F5 /* TokenList.swift */; }; + CCFF7C5A1FB5102C00FFBD89 /* TokenListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B7328E1C0A8AE60076F77E /* TokenListViewModel.swift */; }; + CCFF7C5B1FB5103800FFBD89 /* TokenRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98B1EC91AB2CEC300C59E53 /* TokenRowModel.swift */; }; + CCFF7C5C1FB5104200FFBD89 /* TableDiff.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B7328C1C09495D0076F77E /* TableDiff.swift */; }; + CCFF7C5D1FB5105500FFBD89 /* ProgressRingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D6C84B19075044004F0E08 /* ProgressRingView.swift */; }; + CCFF7C5E1FB5105E00FFBD89 /* DisplayTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = C968D1141CB4C639004ED7BB /* DisplayTime.swift */; }; + CCFF7C5F1FB5108100FFBD89 /* Component.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EB448D1C52A74200ACFA87 /* Component.swift */; }; + CCFF7C601FB5125700FFBD89 /* TokenRowCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C98B1ECB1AB3CF0700C59E53 /* TokenRowCell.swift */; }; + CCFF7C611FB5126400FFBD89 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = C93AD15119CD51BE007480E9 /* Colors.swift */; }; + CCFF7C621FB5137900FFBD89 /* UITableView+ReusableCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BA64E91C0C4FBC00610C7C /* UITableView+ReusableCells.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -82,6 +106,13 @@ remoteGlobalIDString = 1D6058900D05DD3D006BFB54; remoteInfo = Authenticator; }; + CCFF7C211FB4FCA100FFBD89 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = CCFF7C181FB4FCA100FFBD89; + remoteInfo = AuthenticatorTokenProvider; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -98,6 +129,17 @@ name = "Copy Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; + CCFF7C271FB4FCA100FFBD89 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + CCFF7C231FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -182,12 +224,25 @@ C9EB448D1C52A74200ACFA87 /* Component.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Component.swift; sourceTree = ""; }; C9EB448F1C52AE4500ACFA87 /* AppController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppController.swift; sourceTree = ""; }; C9F7A8601C4D90B50082E5AE /* TokenStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenStore.swift; sourceTree = ""; }; + CC00FE301FB66BD600A327C0 /* PasswordPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordPickerViewController.swift; sourceTree = ""; }; + CC00FE361FB670D100A327C0 /* OpaqueNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueNavigationController.swift; sourceTree = ""; }; + CC00FE381FB6712400A327C0 /* ModelBasedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelBasedViewController.swift; sourceTree = ""; }; + CC00FE3C1FB7884100A327C0 /* Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Root.swift; sourceTree = ""; }; + CC00FE3F1FB806BF00A327C0 /* ExtensionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionController.swift; sourceTree = ""; }; + CC00FE431FB8275300A327C0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; CC46C4701DB3007D00EB4605 /* SearchField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; CC46C4721DB3040300EB4605 /* TokenListTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListTests.swift; sourceTree = ""; }; CC471EEA1DC027A6006858AC /* TokenListViewControllerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenListViewControllerTest.swift; sourceTree = ""; }; CC471EEC1DC1377F006858AC /* MockTableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockTableView.swift; sourceTree = ""; }; CCC409751DB28BFE000A050D /* TableDiffTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableDiffTests.swift; sourceTree = ""; }; CCCD668A1E1C74B4005FE96E /* OneTimePasswordExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OneTimePasswordExtensions.swift; sourceTree = ""; }; + CCFF7C191FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = AuthenticatorTokenProvider.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + CCFF7C1B1FB4FCA100FFBD89 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; + CCFF7C201FB4FCA100FFBD89 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + CCFF7C281FB4FCCA00FFBD89 /* Authenticator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Authenticator.entitlements; sourceTree = ""; }; + CCFF7C291FB4FCD200FFBD89 /* AuthenticatorTokenProvider.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AuthenticatorTokenProvider.entitlements; sourceTree = ""; }; + CCFF7C2A1FB4FF2E00FFBD89 /* Picker.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Picker.js; sourceTree = ""; }; + CCFF7C311FB50AC000FFBD89 /* OneTimePassword.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = OneTimePassword.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -213,6 +268,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + CCFF7C161FB4FCA100FFBD89 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CCFF7C301FB50AC000FFBD89 /* OneTimePassword.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -239,6 +302,7 @@ 1D6058910D05DD3D006BFB54 /* Authenticator.app */, C9906A2E1812522100BAEF53 /* AuthenticatorTests.xctest */, C9A262CD1E170BD4004E6CEB /* AuthenticatorScreenshots.xctest */, + CCFF7C191FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex */, ); name = Products; sourceTree = ""; @@ -249,6 +313,7 @@ C97B68CB17D964D4005D1FE0 /* Authenticator */, C9906A331812522100BAEF53 /* AuthenticatorTests */, C9A262CE1E170BD4004E6CEB /* AuthenticatorScreenshots */, + CCFF7C1A1FB4FCA100FFBD89 /* AuthenticatorTokenProvider */, 29B97323FDCFA39411CA2CEA /* Frameworks */, C944A5661A7F772500E08B1E /* Configuration */, 19C28FACFE9D520D11CA2CBB /* Products */, @@ -276,6 +341,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + CCFF7C311FB50AC000FFBD89 /* OneTimePassword.framework */, C944A5581A7ECB3100E08B1E /* Base32.framework */, C944A5561A7ECB0800E08B1E /* OneTimePassword.framework */, C97CDF241BEEA49300D64406 /* SVProgressHUD.framework */, @@ -356,6 +422,7 @@ C97B68CB17D964D4005D1FE0 /* Authenticator */ = { isa = PBXGroup; children = ( + CCFF7C281FB4FCCA00FFBD89 /* Authenticator.entitlements */, 080E96DDFE201D6D7F000001 /* Source */, 29B97317FDCFA39411CA2CEA /* Resources */, ); @@ -379,6 +446,8 @@ C93BD6221C167CD100FFFB8F /* Root.swift */, C93BD6281C168EBF00FFFB8F /* RootViewModel.swift */, C93BD6241C16841D00FFFB8F /* RootViewController.swift */, + CC00FE361FB670D100A327C0 /* OpaqueNavigationController.swift */, + CC00FE381FB6712400A327C0 /* ModelBasedViewController.swift */, ); name = "Root Component"; sourceTree = ""; @@ -505,6 +574,45 @@ name = "Data Store"; sourceTree = ""; }; + CC00FE3B1FB7881300A327C0 /* Components */ = { + isa = PBXGroup; + children = ( + CC00FE3C1FB7884100A327C0 /* Root.swift */, + ); + path = Components; + sourceTree = ""; + }; + CC00FE411FB817CB00A327C0 /* Source */ = { + isa = PBXGroup; + children = ( + CCFF7C1B1FB4FCA100FFBD89 /* ActionViewController.swift */, + CC00FE301FB66BD600A327C0 /* PasswordPickerViewController.swift */, + CC00FE3F1FB806BF00A327C0 /* ExtensionController.swift */, + CCFF7C2A1FB4FF2E00FFBD89 /* Picker.js */, + CC00FE3B1FB7881300A327C0 /* Components */, + ); + path = Source; + sourceTree = ""; + }; + CC00FE421FB8186800A327C0 /* Resources */ = { + isa = PBXGroup; + children = ( + CC00FE431FB8275300A327C0 /* Images.xcassets */, + ); + path = Resources; + sourceTree = ""; + }; + CCFF7C1A1FB4FCA100FFBD89 /* AuthenticatorTokenProvider */ = { + isa = PBXGroup; + children = ( + CCFF7C291FB4FCD200FFBD89 /* AuthenticatorTokenProvider.entitlements */, + CCFF7C201FB4FCA100FFBD89 /* Info.plist */, + CC00FE421FB8186800A327C0 /* Resources */, + CC00FE411FB817CB00A327C0 /* Source */, + ); + path = AuthenticatorTokenProvider; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -517,10 +625,12 @@ C910ADBF1BF00ABF00C988F5 /* Lint */, 1D60588F0D05DD3D006BFB54 /* Frameworks */, C944A5531A7ECA5000E08B1E /* Copy Frameworks */, + CCFF7C271FB4FCA100FFBD89 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + CCFF7C221FB4FCA100FFBD89 /* PBXTargetDependency */, ); name = Authenticator; productName = Authenticator; @@ -562,6 +672,23 @@ productReference = C9A262CD1E170BD4004E6CEB /* AuthenticatorScreenshots.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + CCFF7C181FB4FCA100FFBD89 /* AuthenticatorTokenProvider */ = { + isa = PBXNativeTarget; + buildConfigurationList = CCFF7C261FB4FCA100FFBD89 /* Build configuration list for PBXNativeTarget "AuthenticatorTokenProvider" */; + buildPhases = ( + CCFF7C151FB4FCA100FFBD89 /* Sources */, + CCFF7C161FB4FCA100FFBD89 /* Frameworks */, + CCFF7C171FB4FCA100FFBD89 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AuthenticatorTokenProvider; + productName = AuthenticatorTokenProvider; + productReference = CCFF7C191FB4FCA100FFBD89 /* AuthenticatorTokenProvider.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -578,6 +705,11 @@ DevelopmentTeam = WD7ETSN9J9; LastSwiftMigration = 0830; ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Keychain = { + enabled = 1; + }; + }; }; C9906A2D1812522100BAEF53 = { LastSwiftMigration = 0830; @@ -587,6 +719,15 @@ CreatedOnToolsVersion = 8.2.1; TestTargetID = 1D6058900D05DD3D006BFB54; }; + CCFF7C181FB4FCA100FFBD89 = { + DevelopmentTeam = WD7ETSN9J9; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Keychain = { + enabled = 1; + }; + }; + }; }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Authenticator" */; @@ -603,6 +744,7 @@ 1D6058900D05DD3D006BFB54 /* Authenticator */, C9906A2D1812522100BAEF53 /* AuthenticatorTests */, C9A262CC1E170BD4004E6CEB /* AuthenticatorScreenshots */, + CCFF7C181FB4FCA100FFBD89 /* AuthenticatorTokenProvider */, ); }; /* End PBXProject section */ @@ -626,6 +768,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + CCFF7C171FB4FCA100FFBD89 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCFF7C2B1FB4FF2E00FFBD89 /* Picker.js in Resources */, + CC00FE441FB8275300A327C0 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -650,7 +801,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CC00FE391FB6712400A327C0 /* ModelBasedViewController.swift in Sources */, C93AD15219CD51BE007480E9 /* Colors.swift in Sources */, + CC00FE371FB670D100A327C0 /* OpaqueNavigationController.swift in Sources */, C9DE02E71ED2234D00D7E01C /* InfoList.swift in Sources */, C93BD6251C16841D00FFFB8F /* RootViewController.swift in Sources */, C9919CE01BA721A1006237C1 /* ButtonHeaderView.swift in Sources */, @@ -717,6 +870,31 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + CCFF7C151FB4FCA100FFBD89 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCFF7C5D1FB5105500FFBD89 /* ProgressRingView.swift in Sources */, + CCFF7C611FB5126400FFBD89 /* Colors.swift in Sources */, + CC00FE311FB66BD600A327C0 /* PasswordPickerViewController.swift in Sources */, + CCFF7C5F1FB5108100FFBD89 /* Component.swift in Sources */, + CC00FE321FB66C1C00A327C0 /* SearchField.swift in Sources */, + CC00FE401FB806BF00A327C0 /* ExtensionController.swift in Sources */, + CC00FE3A1FB6719200A327C0 /* OpaqueNavigationController.swift in Sources */, + CCFF7C5E1FB5105E00FFBD89 /* DisplayTime.swift in Sources */, + CCFF7C621FB5137900FFBD89 /* UITableView+ReusableCells.swift in Sources */, + CCFF7C5A1FB5102C00FFBD89 /* TokenListViewModel.swift in Sources */, + CC00FE3D1FB7884100A327C0 /* Root.swift in Sources */, + CCFF7C601FB5125700FFBD89 /* TokenRowCell.swift in Sources */, + CCFF7C581FB50EE100FFBD89 /* ActionViewController.swift in Sources */, + CCFF7C591FB5102500FFBD89 /* TokenList.swift in Sources */, + CC00FE3E1FB790E500A327C0 /* ModelBasedViewController.swift in Sources */, + CCFF7C5B1FB5103800FFBD89 /* TokenRowModel.swift in Sources */, + CCFF7C561FB50E3500FFBD89 /* TokenStore.swift in Sources */, + CCFF7C5C1FB5104200FFBD89 /* TableDiff.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -730,6 +908,11 @@ target = 1D6058900D05DD3D006BFB54 /* Authenticator */; targetProxy = C9A262D21E170BD4004E6CEB /* PBXContainerItemProxy */; }; + CCFF7C221FB4FCA100FFBD89 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = CCFF7C181FB4FCA100FFBD89 /* AuthenticatorTokenProvider */; + targetProxy = CCFF7C211FB4FCA100FFBD89 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -737,8 +920,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = C944A5731A7F772600E08B1E /* iOS-Application.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; BUNDLE_DISPLAY_NAME = "${PRODUCT_NAME} ∆"; + CODE_SIGN_ENTITLEMENTS = Authenticator/Authenticator.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = WD7ETSN9J9; @@ -755,8 +941,11 @@ isa = XCBuildConfiguration; baseConfigurationReference = C944A5731A7F772600E08B1E /* iOS-Application.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; BUNDLE_DISPLAY_NAME = "${PRODUCT_NAME}"; + CODE_SIGN_ENTITLEMENTS = Authenticator/Authenticator.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = WD7ETSN9J9; @@ -840,6 +1029,132 @@ }; name = Release; }; + CCFF7C241FB4FCA100FFBD89 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = ExtensionIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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 = AuthenticatorTokenProvider/AuthenticatorTokenProvider.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = WD7ETSN9J9; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = AuthenticatorTokenProvider/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = me.mattrubin.authenticator.dev.AuthenticatorTokenProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + CCFF7C251FB4FCA100FFBD89 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = ExtensionIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + 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 = AuthenticatorTokenProvider/AuthenticatorTokenProvider.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = WD7ETSN9J9; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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; + INFOPLIST_FILE = AuthenticatorTokenProvider/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = me.mattrubin.authenticator.AuthenticatorTokenProvider; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = 1; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -879,6 +1194,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + CCFF7C261FB4FCA100FFBD89 /* Build configuration list for PBXNativeTarget "AuthenticatorTokenProvider" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCFF7C241FB4FCA100FFBD89 /* Debug */, + CCFF7C251FB4FCA100FFBD89 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; diff --git a/Authenticator/Authenticator.entitlements b/Authenticator/Authenticator.entitlements new file mode 100644 index 00000000..4dabe27e --- /dev/null +++ b/Authenticator/Authenticator.entitlements @@ -0,0 +1,10 @@ + + + + + keychain-access-groups + + $(AppIdentifierPrefix)me.mattrubin.authenticator.dev + + + diff --git a/Authenticator/Source/ModelBasedViewController.swift b/Authenticator/Source/ModelBasedViewController.swift new file mode 100644 index 00000000..4a62c768 --- /dev/null +++ b/Authenticator/Source/ModelBasedViewController.swift @@ -0,0 +1,17 @@ +// +// ModelBasedViewController.swift +// Authenticator +// +// Created by Beau Collins on 11/10/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import Foundation + +protocol ModelBasedViewController { + associatedtype ViewModel + associatedtype Action + + init(viewModel: ViewModel, dispatchAction: @escaping (Action) -> Void) + func updateWithViewModel(_ viewModel: ViewModel) +} diff --git a/Authenticator/Source/OpaqueNavigationController.swift b/Authenticator/Source/OpaqueNavigationController.swift new file mode 100644 index 00000000..24888370 --- /dev/null +++ b/Authenticator/Source/OpaqueNavigationController.swift @@ -0,0 +1,31 @@ +// +// OpaqueNavigationController.swift +// Authenticator +// +// Created by Beau Collins on 11/10/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import UIKit + +class OpaqueNavigationController: UINavigationController { + override func viewDidLoad() { + super.viewDidLoad() + + navigationBar.isTranslucent = false + navigationBar.barTintColor = UIColor.otpBarBackgroundColor + navigationBar.tintColor = UIColor.otpBarForegroundColor + navigationBar.titleTextAttributes = [ + NSForegroundColorAttributeName: UIColor.otpBarForegroundColor, + NSFontAttributeName: UIFont.systemFont(ofSize: 20, weight: UIFontWeightLight), + ] + + toolbar.isTranslucent = false + toolbar.barTintColor = UIColor.otpBarBackgroundColor + toolbar.tintColor = UIColor.otpBarForegroundColor + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } +} diff --git a/Authenticator/Source/RootViewController.swift b/Authenticator/Source/RootViewController.swift index 7fadf21f..8356a9b1 100644 --- a/Authenticator/Source/RootViewController.swift +++ b/Authenticator/Source/RootViewController.swift @@ -25,28 +25,6 @@ import UIKit -class OpaqueNavigationController: UINavigationController { - override func viewDidLoad() { - super.viewDidLoad() - - navigationBar.isTranslucent = false - navigationBar.barTintColor = UIColor.otpBarBackgroundColor - navigationBar.tintColor = UIColor.otpBarForegroundColor - navigationBar.titleTextAttributes = [ - NSForegroundColorAttributeName: UIColor.otpBarForegroundColor, - NSFontAttributeName: UIFont.systemFont(ofSize: 20, weight: UIFontWeightLight), - ] - - toolbar.isTranslucent = false - toolbar.barTintColor = UIColor.otpBarBackgroundColor - toolbar.tintColor = UIColor.otpBarForegroundColor - } - - override var preferredStatusBarStyle: UIStatusBarStyle { - return .lightContent - } -} - class RootViewController: OpaqueNavigationController { fileprivate var currentViewModel: Root.ViewModel @@ -101,14 +79,6 @@ class RootViewController: OpaqueNavigationController { } } -protocol ModelBasedViewController { - associatedtype ViewModel - associatedtype Action - - init(viewModel: ViewModel, dispatchAction: @escaping (Action) -> Void) - func updateWithViewModel(_ viewModel: ViewModel) -} - extension TokenScannerViewController: ModelBasedViewController {} extension TokenFormViewController: ModelBasedViewController {} extension InfoListViewController: ModelBasedViewController {} diff --git a/Authenticator/Source/TokenRowModel.swift b/Authenticator/Source/TokenRowModel.swift index d7466e56..37fefb52 100644 --- a/Authenticator/Source/TokenRowModel.swift +++ b/Authenticator/Source/TokenRowModel.swift @@ -64,12 +64,14 @@ struct TokenRowModel: Identifiable { // Group the password into chunks of two digits, separated by spaces. private static func chunkPassword(_ password: String) -> String { - var characters = password.characters + guard var characters = String(password) else { + return password + } let chunkSize = 2 for i in stride(from: chunkSize, to: characters.count, by: chunkSize).reversed() { characters.insert(" ", at: characters.index(characters.startIndex, offsetBy: i)) } - return String(characters) + return characters } } diff --git a/AuthenticatorTokenProvider/AuthenticatorTokenProvider.entitlements b/AuthenticatorTokenProvider/AuthenticatorTokenProvider.entitlements new file mode 100644 index 00000000..4dabe27e --- /dev/null +++ b/AuthenticatorTokenProvider/AuthenticatorTokenProvider.entitlements @@ -0,0 +1,10 @@ + + + + + keychain-access-groups + + $(AppIdentifierPrefix)me.mattrubin.authenticator.dev + + + diff --git a/AuthenticatorTokenProvider/Info.plist b/AuthenticatorTokenProvider/Info.plist new file mode 100644 index 00000000..ce878dd8 --- /dev/null +++ b/AuthenticatorTokenProvider/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Choose One Time Password + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + NSExtension + + NSExtensionAttributes + + NSExtensionActivationRule + + NSExtensionActivationSupportsWebPageWithMaxCount + 1 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 + + NSExtensionJavaScriptPreprocessingFile + Picker + + NSExtensionPointIdentifier + com.apple.ui-services + NSExtensionPrincipalClass + ActionViewController + + + diff --git a/AuthenticatorTokenProvider/Resources/Images.xcassets/Contents.json b/AuthenticatorTokenProvider/Resources/Images.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/AuthenticatorTokenProvider/Resources/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/Contents.json b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/Contents.json new file mode 100644 index 00000000..d76ba1a6 --- /dev/null +++ b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/Contents.json @@ -0,0 +1,55 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "ExtensionIcon2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "ExtensionIcon3x.png", + "scale" : "3x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon2x.png b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon2x.png new file mode 100644 index 00000000..fefc063a Binary files /dev/null and b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon2x.png differ diff --git a/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon3x.png b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon3x.png new file mode 100644 index 00000000..5ffcbca4 Binary files /dev/null and b/AuthenticatorTokenProvider/Resources/Images.xcassets/ExtensionIcon.appiconset/ExtensionIcon3x.png differ diff --git a/AuthenticatorTokenProvider/Source/ActionViewController.swift b/AuthenticatorTokenProvider/Source/ActionViewController.swift new file mode 100644 index 00000000..12120bab --- /dev/null +++ b/AuthenticatorTokenProvider/Source/ActionViewController.swift @@ -0,0 +1,41 @@ +// +// ActionViewController.swift +// AuthenticatorTokenProvider +// +// Created by Beau Collins on 11/9/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import UIKit +import Foundation +import MobileCoreServices + +@objc(ActionViewController) + +class ActionViewController: UIViewController { + + lazy var extensionController: ExtensionController = { + return ExtensionController(withContext: self.extensionContext!) + }() + + override func viewDidLoad() { + // Get context from the webpage to highlight potential passwords first + super.viewDidLoad() + present(extensionController.rootViewController, animated: false, completion: nil) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.extensionController.rootViewController.preferredStatusBarStyle + } + + override func loadView() { + self.view = UIView(frame: UIScreen.main.bounds) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + +} + diff --git a/AuthenticatorTokenProvider/Source/Components/Root.swift b/AuthenticatorTokenProvider/Source/Components/Root.swift new file mode 100644 index 00000000..845840e9 --- /dev/null +++ b/AuthenticatorTokenProvider/Source/Components/Root.swift @@ -0,0 +1,85 @@ +// +// Root.swift +// AuthenticatorTokenProvider +// +// Created by Beau Collins on 11/11/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import Foundation +import OneTimePassword +import MobileCoreServices + +struct Picker: Component { + + enum Action { + case cancel + case tokenListAction(action: TokenList.Action) + } + + enum Effect { + + } + + var tokenList: TokenList + let extensionContext: NSExtensionContext + + init(extensionContext context: NSExtensionContext) { + tokenList = TokenList() + extensionContext = context + } + + mutating func update(_ action: Picker.Action) -> Picker.Effect? { + switch action { + case .tokenListAction(let action): + let effect = tokenList.update(action).flatMap { effect in handleTokenListEffect(effect) } + switch action { + case .copyPassword(let password): + completeRequest(withPassword: password) + return nil + default: + return effect + } + case .cancel: + completeRequest() + return nil + } + } + + private func handleTokenListEffect(_ effect: TokenList.Effect) -> Picker.Effect? { + // honestly nothing needs to happen + return nil + } + + struct ViewModel { + var pageContext: [String] + var tokenList: TokenListViewModel; + } + + func completeRequest(withPassword password: String? = nil) { + guard let password = password else { + extensionContext.completeRequest(returningItems: nil, completionHandler: nil) + return + } + + let item = NSExtensionItem() + let contents: NSDictionary = [ + NSExtensionJavaScriptFinalizeArgumentKey: ["password" : password ] + ] + let passwordItemProvider = NSItemProvider(item: contents, typeIdentifier: kUTTypePropertyList as String) + item.attachments = [passwordItemProvider] + extensionContext.completeRequest(returningItems: [item]) + } +} + +extension Picker { + func viewModel(for tokens: [PersistentToken], at time: DisplayTime) -> (viewModel: ViewModel, nextRefreshTime: Date) { + let (tokenListViewModel, nextRefreshTime) = tokenList.viewModel(for: tokens, at: time) + let viewModel = ViewModel( + pageContext: [], + tokenList: tokenListViewModel + ) + return (viewModel: viewModel, nextRefreshTime: nextRefreshTime) + } +} + diff --git a/AuthenticatorTokenProvider/Source/ExtensionController.swift b/AuthenticatorTokenProvider/Source/ExtensionController.swift new file mode 100644 index 00000000..df6bc485 --- /dev/null +++ b/AuthenticatorTokenProvider/Source/ExtensionController.swift @@ -0,0 +1,111 @@ +// +// AppController.swift +// AuthenticatorTokenProvider +// +// Created by Beau Collins on 11/11/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import Foundation +import OneTimePassword +import MobileCoreServices + +class ExtensionController { + + var component: Picker { + didSet { updateView() } + } + + let store: TokenStore + + var nextRefreshTime: Date = Date.distantPast { + willSet { + self.timer?.invalidate() + } + didSet { + switch self.nextRefreshTime { + case .distantPast: + return + case .distantFuture: + return + default: + let timer = Timer(fireAt: self.nextRefreshTime, + interval: 0, + target: self, + selector: #selector(updateView), + userInfo: nil, + repeats: false) + // Add the new timer to the main run loop + RunLoop.main.add(timer, forMode: .commonModes) + self.timer = timer + } + } + } + + var timer: Timer? + + lazy var rootViewController: OpaqueNavigationController = { + return OpaqueNavigationController(rootViewController: self.passwordPicker) + }(); + + lazy var passwordPicker: PasswordPickerViewController = { + let (viewModel, nextRefreshTime) = self.component.viewModel(for: self.store.persistentTokens, at: self.now()) + self.nextRefreshTime = nextRefreshTime + return PasswordPickerViewController(viewModel: viewModel, dispatchAction: self.handleAction) + }() + + init(withContext context: NSExtensionContext) { + do { + store = try KeychainTokenStore( + keychain: Keychain.sharedInstance, + userDefaults: UserDefaults.standard + ) + } catch { + // If the TokenStore could not be created, the app is unusable. + fatalError("Failed to load token store: \(error)") + } + component = Picker(extensionContext: context) + prepareContext() + } + + func handleAction(_ action: Picker.Action) { + print("handle action \(action)") + if let effect = component.update(action) { + print("handle side effect \(effect)") + handleEffect(effect) + } + } + + func handleEffect(_ effect: Picker.Effect) { + } + + @objc + func updateView() { + let (viewModel, nextRefreshTime) = component.viewModel(for: store.persistentTokens, at: now()) + self.nextRefreshTime = nextRefreshTime + passwordPicker.updateWithViewModel(viewModel) + } + + func prepareContext() { + for item in self.component.extensionContext.inputItems as! [NSExtensionItem] { + print("Input item \(item.attachments ?? [])") + if let provider = item.attachments?.first as? NSItemProvider { + provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil) { (data, error) in + guard error == nil else { + print("failure \(error)") + return + } + guard let result = data as? NSDictionary else { + print("not valid data") + return; + } + print("Provider: \(result["baseURI"] ?? "no uri")") + } + } + } + } + + func now() -> DisplayTime { + return DisplayTime(date: Date()) + } +} diff --git a/AuthenticatorTokenProvider/Source/PasswordPickerViewController.swift b/AuthenticatorTokenProvider/Source/PasswordPickerViewController.swift new file mode 100644 index 00000000..5e0fe968 --- /dev/null +++ b/AuthenticatorTokenProvider/Source/PasswordPickerViewController.swift @@ -0,0 +1,119 @@ +// +// PasswordPickerController.swift +// AuthenticatorTokenProvider +// +// Created by Beau Collins on 11/10/17. +// Copyright © 2017 Matt Rubin. All rights reserved. +// + +import UIKit + +class PasswordPickerViewController: UITableViewController { + + fileprivate let dispatchAction: (Picker.Action) -> Void + fileprivate var viewModel: Picker.ViewModel + fileprivate var ignoreTableViewUpdates = false + + fileprivate var searchBar = SearchField( + frame: CGRect( + origin: .zero, + size: CGSize(width: 0, height: 44) + ) + ) + + required init(viewModel: Picker.ViewModel, dispatchAction: @escaping (Picker.Action) -> Void) { + self.viewModel = viewModel + self.dispatchAction = dispatchAction + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateWithViewModel(_ viewModel: Picker.ViewModel) { + self.viewModel = viewModel + tableView.reloadData() + updatePeripheralViews() + } + + func updatePeripheralViews() { + self.searchBar.updateWithViewModel(viewModel.tokenList) + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .lightContent + } + + override func viewDidLoad() { + super.viewDidLoad() + + self.title = "Authenticator" + self.view.backgroundColor = UIColor.otpBackgroundColor + + // Configure table view + self.tableView.separatorStyle = .none + self.tableView.indicatorStyle = .white + self.tableView.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 0, right: 0) + self.tableView.allowsSelectionDuringEditing = true + + self.navigationItem.titleView = searchBar + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, + target: self, + action: #selector(cancelPicker)) + + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.updatePeripheralViews() + + self.searchBar.textField.addTarget(self, action: #selector(filterTokens), for: .editingChanged) + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + func filterTokens() { + guard let filter = searchBar.text else { + return dispatchAction(.tokenListAction(action: .clearFilter)) + } + dispatchAction(.tokenListAction(action: .filter(filter))) + } + + func cancelPicker() { + dispatchAction(.cancel) + } + + // MARK: - Table view data source + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return viewModel.tokenList.rowModels.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCellWithClass(TokenRowCell.self) + updateCell(cell, forRowAtIndexPath: indexPath) + return cell + } + + fileprivate func updateCell(_ cell: TokenRowCell, forRowAtIndexPath indexPath: IndexPath) { + let rowModel = viewModel.tokenList.rowModels[indexPath.row] + cell.updateWithRowModel(rowModel) + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 85 + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let rowModel = viewModel.tokenList.rowModels[indexPath.row] + dispatchAction(.tokenListAction(action: rowModel.selectAction)) + } + +} + +extension PasswordPickerViewController: ModelBasedViewController { +} diff --git a/AuthenticatorTokenProvider/Source/Picker.js b/AuthenticatorTokenProvider/Source/Picker.js new file mode 100644 index 00000000..955d1bee --- /dev/null +++ b/AuthenticatorTokenProvider/Source/Picker.js @@ -0,0 +1,42 @@ + +function setPassword(password) { + return function (inputNode) { + inputNode.value = password; + } +} + +/* + * Javascript files used for action processing are expected to + * define a global variable of ExtensionPreprocessingJS that can + * have two methods/functions: + * + * - run: executed by the action extension when it wants to get data from the webpage + * - finalize: executed by the action extension when it completes its activity + */ +var ExtensionPreprocessingJS = { + /* + * When an action is initialized it can ask to run this script + * to provide context to the action + */ + run: function(arguments) { + // provide the current URI + // the share extension can use this information to + // highlight what it thinks is the correct password + arguments.completionFunction({ + baseURI: document.baseURI + }); + }, + /* + * Called when the action has completed picking a password + */ + finalize: function(arguments) { + // usually OTP fields are type=tel, but we can't assume this is the case + // as a fallback, the extension should copy the password to the clipboard + // as well + const potentialFields = document.querySelectorAll( 'input[type=text],input[type=tel]' ); + // Use the Array forEach iterator to set the input's value to + // the provided password + [].forEach.call(potentialFields, setPassword(arguments.password)) + } +}; +