diff --git a/Lock/ClassicRouter.swift b/Lock/ClassicRouter.swift index e52ea9d0c..4e911e6c9 100644 --- a/Lock/ClassicRouter.swift +++ b/Lock/ClassicRouter.swift @@ -51,10 +51,8 @@ struct ClassicRouter: Router { guard self.lock.options.allow != [.ResetPassword] && self.lock.options.initialScreen != .resetPassword else { return forgotPassword } let authentication = self.lock.authentication let interactor = DatabaseInteractor(connection: database, authentication: authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore) + self.lock.options.passwordManager.controller = self.controller let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options) - if self.lock.options.passwordManager.enabled, OnePassword.isAvailable() { - presenter.passwordManager = OnePassword(withConfig: self.lock.options.passwordManager, controller: self.controller) - } if !oauth2.isEmpty { let interactor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers) presenter.authPresenter = AuthPresenter(connections: oauth2, interactor: interactor, customStyle: self.lock.style.oauth2) diff --git a/Lock/DatabaseOnlyView.swift b/Lock/DatabaseOnlyView.swift index a07d557a9..9ea8b6dd0 100644 --- a/Lock/DatabaseOnlyView.swift +++ b/Lock/DatabaseOnlyView.swift @@ -86,7 +86,7 @@ class DatabaseOnlyView: UIView, DatabaseView { private let separatorIndex = 2 private let socialIndex = 1 - func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil, passwordManager: PasswordManager?) { + func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil, passwordManager: PasswordManager) { let form = CredentialView() let type: InputField.InputType @@ -109,15 +109,15 @@ class DatabaseOnlyView: UIView, DatabaseView { self.layoutSecondaryButton(self.allowedModes.contains(.ResetPassword)) self.form = form - if var passwordManager = passwordManager { + if passwordManager.enabled, passwordManager.isAvailable() { self.passwordManagerButton = form.passwordField.addFieldButton(withIcon: "ic_onepassword", color: UIColor(red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0)) - passwordManager.fields[AppExtensionUsernameKey] = form.identityField - passwordManager.fields[AppExtensionPasswordKey] = form.passwordField + passwordManager.setFields([AppExtensionUsernameKey: form.identityField, + AppExtensionPasswordKey: form.passwordField]) } } - func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView? = nil, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator? = nil, passwordManager: PasswordManager?) { + func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView? = nil, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator? = nil, passwordManager: PasswordManager) { let form = SignUpView(additionalFields: additionalFields) form.showUsername = showUsername form.emailField.text = email @@ -154,10 +154,10 @@ class DatabaseOnlyView: UIView, DatabaseView { } } - if var passwordManager = passwordManager { + if passwordManager.enabled, passwordManager.isAvailable() { self.passwordManagerButton = form.passwordField.addFieldButton(withIcon: "ic_onepassword", color: UIColor(red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0)) - passwordManager.fields[AppExtensionUsernameKey] = showUsername ? form.usernameField : form.emailField - passwordManager.fields[AppExtensionPasswordKey] = form.passwordField + passwordManager.setFields([AppExtensionUsernameKey: showUsername ? form.usernameField : form.emailField, + AppExtensionPasswordKey: form.passwordField ]) } } diff --git a/Lock/DatabasePresenter.swift b/Lock/DatabasePresenter.swift index b8009c318..a7ba7765f 100644 --- a/Lock/DatabasePresenter.swift +++ b/Lock/DatabasePresenter.swift @@ -38,7 +38,7 @@ class DatabasePresenter: Presentable, Loggable { } } - var passwordManager: PasswordManager? + var passwordManager: PasswordManager var authPresenter: AuthPresenter? var enterpriseInteractor: EnterpriseDomainInteractor? @@ -59,6 +59,7 @@ class DatabasePresenter: Presentable, Loggable { self.database = connection self.navigator = navigator self.options = options + self.passwordManager = options.passwordManager } var view: View { @@ -149,9 +150,9 @@ class DatabasePresenter: Presentable, Loggable { self.navigator.navigate(.forgotPassword) } - if let passwordManager = self.passwordManager, let passwordButton = view.passwordManagerButton { + if self.passwordManager.enabled, let passwordButton = view.passwordManagerButton { passwordButton.onPress = { _ in - passwordManager.login { error, result in + self.passwordManager.login { error, result in guard error == nil else { return self.logger.error("There was a problem with the password manager: \(error)") } @@ -219,9 +220,9 @@ class DatabasePresenter: Presentable, Loggable { self.navigator.present(alert) } - if let passwordManager = self.passwordManager, let passwordButton = view.passwordManagerButton { + if self.passwordManager.enabled, let passwordButton = view.passwordManagerButton { passwordButton.onPress = { _ in - passwordManager.store(withPolicy: passwordPolicyValidator?.policy.onePasswordRules()) { error, result in + self.passwordManager.store(withPolicy: passwordPolicyValidator?.policy.onePasswordRules()) { error, result in guard error == nil else { return self.logger.error("There was a problem with the password manager: \(error)") } diff --git a/Lock/DatabaseView.swift b/Lock/DatabaseView.swift index 52ef17c92..591d9b3ee 100644 --- a/Lock/DatabaseView.swift +++ b/Lock/DatabaseView.swift @@ -33,7 +33,7 @@ protocol DatabaseView: View, NSObjectProtocol { var allowedModes: DatabaseMode { get } - func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String?, authCollectionView: AuthCollectionView?, passwordManager: PasswordManager?) + func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String?, authCollectionView: AuthCollectionView?, passwordManager: PasswordManager) // swiftlint:disable:next function_parameter_count - func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView?, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator?, passwordManager: PasswordManager?) + func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView?, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator?, passwordManager: PasswordManager) } diff --git a/Lock/IconButton.swift b/Lock/IconButton.swift index ec6505966..dd6e7f9b9 100644 --- a/Lock/IconButton.swift +++ b/Lock/IconButton.swift @@ -68,6 +68,7 @@ class IconButton: UIView { button.translatesAutoresizingMaskIntoConstraints = false button.addTarget(self, action: #selector(pressed), for: .touchUpInside) + button.imageEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) self.button = button } diff --git a/Lock/InputField.swift b/Lock/InputField.swift index 0094eaa1f..653507cb6 100644 --- a/Lock/InputField.swift +++ b/Lock/InputField.swift @@ -196,7 +196,6 @@ class InputField: UIView, UITextFieldDelegate { let button = IconButton() button.icon = LazyImage(name: name, bundle: Lock.bundle).image(compatibleWithTraits: self.traitCollection) button.color = color - button.transform = CGAffineTransform(scaleX: 0.75, y: 0.75) container.addSubview(button) self.textFieldRightPadding?.constant = -50 diff --git a/Lock/LockOptions.swift b/Lock/LockOptions.swift index 7be2b58f8..df05edf0e 100644 --- a/Lock/LockOptions.swift +++ b/Lock/LockOptions.swift @@ -47,5 +47,5 @@ struct LockOptions: OptionBuildable { var audience: String? var passwordlessMethod: PasswordlessMethod = .code - var passwordManager: PasswordManagerConfig = PasswordManagerConfig() + var passwordManager: OnePassword = OnePassword() } diff --git a/Lock/OnePassword.swift b/Lock/OnePassword.swift index 4173a8593..d3dbad678 100644 --- a/Lock/OnePassword.swift +++ b/Lock/OnePassword.swift @@ -23,31 +23,45 @@ import Foundation protocol PasswordManager { - var fields: [String: InputField] { get set } - static func isAvailable() -> Bool + var enabled: Bool { get set } + func setFields(_ fields: [String: InputField?]) + + func isAvailable() -> Bool func login(callback: @escaping (Error?, [String: InputField]?) -> Void) func store(withPolicy policy: [String: Any]?, callback: @escaping (Error?, [String: InputField]?) -> Void) } -class OnePassword: PasswordManager { +public class OnePassword: PasswordManager { + + /// A Boolean value indicating whether the password manager is enabled. + public var enabled: Bool = true + + /// The text identifier to use with the password manager to identify which credentials to use. + public var appIdentifier: String + + /// The title to be displayed when creating a new password manager entry. + public var displayName: String - let config: PasswordManagerConfig weak var controller: UIViewController? var fields: [String: InputField] = [:] - required init(withConfig config: PasswordManagerConfig, controller: UIViewController?) { - self.config = config - self.controller = controller + public init() { + self.appIdentifier = Bundle.main.bundleIdentifier! + self.displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String } - static func isAvailable() -> Bool { + func isAvailable() -> Bool { return OnePasswordExtension.shared().isAppExtensionAvailable() } + func setFields(_ fields: [String: InputField?]) { + fields.forEach { self.fields[$0] = $1 } + } + func login(callback: @escaping (Error?, [String: InputField]?) -> Void) { guard let controller = self.controller else { return } - OnePasswordExtension.shared().findLogin(forURLString: self.config.appIdentifier, for: controller, sender: nil) { (result, error) in + OnePasswordExtension.shared().findLogin(forURLString: self.appIdentifier, for: controller, sender: nil) { (result, error) in guard error == nil else { return callback(error, nil) } @@ -59,9 +73,9 @@ class OnePassword: PasswordManager { func store(withPolicy policy: [String: Any]?, callback: @escaping (Error?, [String: InputField]?) -> Void) { guard let controller = self.controller else { return } - var loginDetails: [String: String] = [ AppExtensionTitleKey: self.config.displayName ] + var loginDetails: [String: String] = [ AppExtensionTitleKey: self.displayName ] self.fields.forEach { loginDetails[$0] = $1.text } - OnePasswordExtension.shared().storeLogin(forURLString: self.config.appIdentifier, loginDetails: loginDetails, passwordGenerationOptions: policy, for: controller, sender: nil) { (result, error) in + OnePasswordExtension.shared().storeLogin(forURLString: self.appIdentifier, loginDetails: loginDetails, passwordGenerationOptions: policy, for: controller, sender: nil) { (result, error) in guard error == nil else { return callback(error, nil) } @@ -71,20 +85,3 @@ class OnePassword: PasswordManager { } } } - -public struct PasswordManagerConfig { - - /// A Boolean value indicating whether the password manager is enabled. - public var enabled: Bool = true - - /// The text identifier to use with the password manager to identify which credentials to use. - public var appIdentifier: String - - /// The title to be displayed when creating a new password manager entry. - public var displayName: String - - public init() { - self.appIdentifier = Bundle.main.bundleIdentifier! - self.displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String - } -} diff --git a/Lock/OptionBuildable.swift b/Lock/OptionBuildable.swift index 0edf51d5c..c517fceb1 100644 --- a/Lock/OptionBuildable.swift +++ b/Lock/OptionBuildable.swift @@ -91,8 +91,8 @@ public protocol OptionBuildable: Options { /// Specify the passwordless method, send a passcode or magic link. By default is .code var passwordlessMethod: PasswordlessMethod { get set } - /// Specify the passwordManager configuration, specify the appIdentifier, displyName and enable/disable manager. By default manager is enabled and defaults to the app's bundle identifier and display name. - var passwordManager: PasswordManagerConfig { get set } + /// Specify the password manager configuration, specify the appIdentifier, displyName and enable/disable manager. By default manager is enabled and defaults to the app's bundle identifier and display name. + var passwordManager: OnePassword { get set } } extension OptionBuildable { diff --git a/Lock/Options.swift b/Lock/Options.swift index e11da7fea..b02214bc1 100644 --- a/Lock/Options.swift +++ b/Lock/Options.swift @@ -49,5 +49,5 @@ public protocol Options { var audience: String? { get } var passwordlessMethod: PasswordlessMethod { get } - var passwordManager: PasswordManagerConfig { get } + var passwordManager: OnePassword { get } } diff --git a/LockTests/OnePasswordSpec.swift b/LockTests/OnePasswordSpec.swift index b9cb7134b..ddb629575 100644 --- a/LockTests/OnePasswordSpec.swift +++ b/LockTests/OnePasswordSpec.swift @@ -31,44 +31,46 @@ class OnePasswordSpec: QuickSpec { var onePassword: OnePassword? var viewController: MockViewController? - var passwordConfig: PasswordManagerConfig! - describe("availability") { + describe("init") { - it("should not be available without 1password installed") { - expect(OnePassword.isAvailable()) == false + beforeEach { + onePassword = nil } + it("should init with defaults") { + onePassword = OnePassword() + expect(onePassword?.appIdentifier) == Bundle.main.bundleIdentifier! + expect(onePassword?.displayName) == Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String + expect(onePassword?.enabled) == true + } + + it("should not be available without 1password installed") { + onePassword = OnePassword() + expect(onePassword?.isAvailable()) == false + } + } - describe("init") { + describe("data input") { beforeEach { - onePassword = nil - viewController = MockViewController() - passwordConfig = PasswordManagerConfig() + onePassword = OnePassword() } - it("should init with default config") { - onePassword = OnePassword(withConfig: passwordConfig, controller: nil) - expect(onePassword?.config.appIdentifier) == Bundle.main.bundleIdentifier! - expect(onePassword?.config.displayName) == Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String - expect(onePassword?.config.enabled) == true + it("should contain one inputfield") { + onePassword?.setFields(["field1": InputField()]) + expect(onePassword?.fields.count) == 1 } - it("should init with identifier and controller") { - onePassword = OnePassword(withConfig: passwordConfig, controller: viewController) - expect(onePassword?.controller).to(equal(viewController)) - } - } describe("action") { beforeEach { viewController = MockViewController() - passwordConfig = PasswordManagerConfig() - onePassword = OnePassword(withConfig: passwordConfig, controller: viewController) + onePassword = OnePassword() + onePassword?.controller = viewController } it("should present extension prompt on login") { diff --git a/LockTests/Presenters/DatabasePresenterSpec.swift b/LockTests/Presenters/DatabasePresenterSpec.swift index 51fb60978..eb5818068 100644 --- a/LockTests/Presenters/DatabasePresenterSpec.swift +++ b/LockTests/Presenters/DatabasePresenterSpec.swift @@ -231,6 +231,12 @@ class DatabasePresenterSpec: QuickSpec { it("should show password manager button") { expect(view.passwordManagerButton).toNot(beNil()) } + + it("should not show password manager when disabled") { + presenter.passwordManager.enabled = false + view = presenter.view as! DatabaseOnlyView + expect(view.passwordManagerButton).to(beNil()) + } } describe("user input") { @@ -505,6 +511,22 @@ class DatabasePresenterSpec: QuickSpec { it("should show password manager button") { expect(view.passwordManagerButton).toNot(beNil()) } + + context("disable password manager") { + + beforeEach { + presenter.passwordManager = passwordManager + presenter.passwordManager.enabled = false + view = presenter.view as! DatabaseOnlyView + view.switcher?.selected = .signup + view.switcher?.onSelectionChange(view.switcher!) + } + + it("should not show password manager when disabled") { + expect(view.passwordManagerButton).to(beNil()) + } + + } } } diff --git a/LockTests/Utils/Mocks.swift b/LockTests/Utils/Mocks.swift index abbc204c2..1e6f67109 100644 --- a/LockTests/Utils/Mocks.swift +++ b/LockTests/Utils/Mocks.swift @@ -495,15 +495,21 @@ class MockPasswordlessInteractor: PasswordlessAuthenticatable { } class MockPasswordManager: PasswordManager { - static var available: Bool = true + var available: Bool = true + + var enabled: Bool = true var fields: [String: InputField] = [:] + func setFields(_ fields: [String: InputField?]) { + fields.forEach { self.fields[$0] = $1 } + } + var onLogin: ([String: InputField]) -> Void = {_ in } var onStore: ([String: InputField]) -> Void = {_ in } - static func isAvailable() -> Bool { - return MockPasswordManager.available + func isAvailable() -> Bool { + return self.available } func login(callback: @escaping (Error?, [String: InputField]?) -> Void) { diff --git a/codecov.yml b/codecov.yml index 02d1031a6..658196e9d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,7 +3,7 @@ coverage: round: down range: "70...100" ignore: - - Lock/LockOnePasswordExtension.* + - Lock/OnePasswordExtension.* - LockTests/* - App/* status: