Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1Password Support for Database Connections #422

Merged
merged 4 commits into from
May 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>org-appextension-feature-password-management</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
Expand Down
5 changes: 5 additions & 0 deletions App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class ViewController: UIViewController {
.classic()
.withOptions {
applyDefaultOptions(&$0)
$0.passwordManager.appIdentifier = "www.myapp.com"
$0.passwordManager.displayName = "My App"
$0.customSignupFields = [
CustomTextField(name: "first_name", placeholder: "First Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle)),
CustomTextField(name: "last_name", placeholder: "Last Name", icon: LazyImage(name: "ic_person", bundle: Lock.bundle))
Expand Down Expand Up @@ -255,6 +257,9 @@ func applyPhantomStyle(_ style: inout Style) {

// Table View
style.searchBarStyle = .minimal

// 1 Password
style.onePasswordIconColor = darkPurple
}

class CleanroomLockLogger: LoggerOutput {
Expand Down
2 changes: 1 addition & 1 deletion Lock.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Auth0 is a SaaS that helps you with Authentication and Authorization. You can us
s.default_subspecs = 'Classic'

s.subspec 'Classic' do |classic|
classic.ios.source_files = "Lock/**/*.swift"
classic.ios.source_files = "Lock/**/*.{swift,h,m}"
classic.ios.resource = ["Lock/*.xcassets", "Lock/*.lproj", "Lock/passwordless_country_codes.plist"]
end

Expand Down
180 changes: 107 additions & 73 deletions Lock.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Lock/ClassicRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ 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 !oauth2.isEmpty {
let interactor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
Expand Down
23 changes: 21 additions & 2 deletions Lock/DatabaseOnlyView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class DatabaseOnlyView: UIView, DatabaseView {
weak var ssoBar: InfoBarView?
weak var spacer: UIView?
private var style: Style?
weak var passwordManagerButton: IconButton?

weak var identityField: InputField?
weak var passwordField: InputField?

// FIXME: Remove this from the view since it should not even know it exists
var navigator: Navigable?
Expand Down Expand Up @@ -86,7 +90,7 @@ class DatabaseOnlyView: UIView, DatabaseView {
private let separatorIndex = 2
private let socialIndex = 1

func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil) {
func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String? = nil, authCollectionView: AuthCollectionView? = nil, showPassswordManager: Bool) {
let form = CredentialView()

let type: InputField.InputType
Expand All @@ -108,9 +112,16 @@ class DatabaseOnlyView: UIView, DatabaseView {
layoutInStack(form, authCollectionView: authCollectionView)
self.layoutSecondaryButton(self.allowedModes.contains(.ResetPassword))
self.form = form
self.identityField = form.identityField
self.passwordField = form.passwordField

if showPassswordManager {
self.passwordManagerButton = form.passwordField.addFieldButton(withIcon: "ic_onepassword", color: Style.Auth0.onePasswordIconColor)
}

}

func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView? = nil, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator? = nil) {
func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView? = nil, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator? = nil, showPassswordManager: Bool) {
let form = SignUpView(additionalFields: additionalFields)
form.showUsername = showUsername
form.emailField.text = email
Expand All @@ -125,6 +136,9 @@ class DatabaseOnlyView: UIView, DatabaseView {
self.layoutSecondaryButton(true)
self.form = form

self.identityField = showUsername ? form.usernameField : form.emailField
self.passwordField = form.passwordField

if let passwordPolicyValidator = passwordPolicyValidator {
let passwordPolicyView = PolicyView(rules: passwordPolicyValidator.policy.rules)
passwordPolicyValidator.delegate = passwordPolicyView
Expand All @@ -146,6 +160,10 @@ class DatabaseOnlyView: UIView, DatabaseView {
view.isHidden = true
}
}

if showPassswordManager {
self.passwordManagerButton = form.passwordField.addFieldButton(withIcon: "ic_onepassword", color: Style.Auth0.onePasswordIconColor)
}
}

func presentEnterprise() {
Expand Down Expand Up @@ -244,5 +262,6 @@ class DatabaseOnlyView: UIView, DatabaseView {
func apply(style: Style) {
self.style = style
self.separator?.textColor = style.seperatorTextColor
self.passwordManagerButton?.color = style.onePasswordIconColor
}
}
50 changes: 43 additions & 7 deletions Lock/DatabasePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class DatabasePresenter: Presentable, Loggable {
}
}

var passwordManager: PasswordManager

var authPresenter: AuthPresenter?
var enterpriseInteractor: EnterpriseDomainInteractor?

Expand All @@ -57,6 +59,7 @@ class DatabasePresenter: Presentable, Loggable {
self.database = connection
self.navigator = navigator
self.options = options
self.passwordManager = options.passwordManager
}

var view: View {
Expand Down Expand Up @@ -85,18 +88,18 @@ class DatabasePresenter: Presentable, Loggable {
showSignup(inView: database, username: initialUsername, email: initialEmail)
}
self.databaseView = database

return database
}

private func showLogin(inView view: DatabaseView, identifier: String?) {
self.messagePresenter?.hideCurrent()
let authCollectionView = self.authPresenter?.newViewToEmbed(withInsets: UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20), isLogin: true)
let style = self.database.requiresUsername ? self.options.usernameStyle : [.Email]
view.showLogin(withIdentifierStyle: style, identifier: identifier, authCollectionView: authCollectionView)
view.showLogin(withIdentifierStyle: style, identifier: identifier, authCollectionView: authCollectionView, showPassswordManager: self.passwordManager.available)
self.currentScreen = .login
let form = view.form
form?.onValueChange = self.handleInput

let action = { [weak form] (button: PrimaryButton) in
self.messagePresenter?.hideCurrent()
self.logger.info("Perform login for email: \(self.authenticator.email.verbatim())")
Expand Down Expand Up @@ -146,6 +149,23 @@ class DatabasePresenter: Presentable, Loggable {
view.secondaryButton?.onPress = { button in
self.navigator.navigate(.forgotPassword)
}

if let identifyField = view.identityField, let passwordField = view.passwordField {
passwordManager.onUpdate = { [unowned self, unowned identifyField, unowned passwordField] identifier, password in
identifyField.text = identifier
passwordField.text = password
self.handleInput(identifyField)
self.handleInput(passwordField)
}
}
view.passwordManagerButton?.onPress = { _ in
self.passwordManager.login {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are retaining the presenter here

guard $0 == nil else {
return self.logger.error("There was a problem with the password manager: \($0.verbatim())")
}
}
}

}

private func showSignup(inView view: DatabaseView, username: String?, email: String?) {
Expand All @@ -155,7 +175,7 @@ class DatabasePresenter: Presentable, Loggable {
let passwordPolicyValidator = interactor?.passwordValidator as? PasswordPolicyValidator
self.currentScreen = .signup

view.showSignUp(withUsername: self.database.requiresUsername, username: username, email: email, authCollectionView: authCollectionView, additionalFields: self.options.customSignupFields, passwordPolicyValidator: passwordPolicyValidator)
view.showSignUp(withUsername: self.database.requiresUsername, username: username, email: email, authCollectionView: authCollectionView, additionalFields: self.options.customSignupFields, passwordPolicyValidator: passwordPolicyValidator, showPassswordManager: self.passwordManager.available)
let form = view.form
view.form?.onValueChange = self.handleInput
let action = { [weak form] (button: PrimaryButton) in
Expand Down Expand Up @@ -186,7 +206,6 @@ class DatabasePresenter: Presentable, Loggable {
form?.needsToUpdateState()
self.messagePresenter?.showError(error)
self.logger.error("Failed with error \(error)")

}
}
}
Expand All @@ -206,6 +225,23 @@ class DatabasePresenter: Presentable, Loggable {
[cancel, tos, privacy].forEach { alert.addAction($0) }
self.navigator.present(alert)
}

if let identifyField = view.identityField, let passwordField = view.passwordField {
passwordManager.onUpdate = { [unowned self, unowned identifyField, unowned passwordField] identifier, password in
identifyField.text = identifier
passwordField.text = password
self.handleInput(identifyField)
self.handleInput(passwordField)
}
}
view.passwordManagerButton?.onPress = { _ in
self.passwordManager.store(withPolicy: passwordPolicyValidator?.policy.onePasswordRules(), identifier: self.creator.identifier) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are retaining the presenter here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both, there is no strong reference to the block which is why I didn't modify in the last pass. Quick checked in DatabasePresenter.swift with:

deinit {
    print("dealloc")
}

guard $0 == nil else {
return self.logger.error("There was a problem with the password manager: \($0.verbatim())")
}
}
}

}

private func handleInput(_ input: InputField) {
Expand Down Expand Up @@ -238,9 +274,9 @@ class DatabasePresenter: Presentable, Loggable {
input.showValid()

guard
let mode = self.databaseView?.switcher?.selected,
mode == .login && updateHRD
else { return }
let mode = self.databaseView?.switcher?.selected,
mode == .login && updateHRD
else { return }
try? self.enterpriseInteractor?.updateEmail(input.text)
if let connection = self.enterpriseInteractor?.connection {
self.logger.verbose("Enterprise connection detected: \(connection)")
Expand Down
7 changes: 5 additions & 2 deletions Lock/DatabaseView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ protocol DatabaseView: class, View {
weak var secondaryButton: SecondaryButton? { get }
weak var primaryButton: PrimaryButton? { get }
weak var switcher: DatabaseModeSwitcher? { get }
weak var passwordManagerButton: IconButton? { get }
weak var identityField: InputField? { get }
weak var passwordField: InputField? { get }

var traitCollection: UITraitCollection { get }

var allowedModes: DatabaseMode { get }

func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String?, authCollectionView: AuthCollectionView?)
func showLogin(withIdentifierStyle style: DatabaseIdentifierStyle, identifier: String?, authCollectionView: AuthCollectionView?, showPassswordManager: Bool)
// swiftlint:disable:next function_parameter_count
func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView?, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator?)
func showSignUp(withUsername showUsername: Bool, username: String?, email: String?, authCollectionView: AuthCollectionView?, additionalFields: [CustomTextField], passwordPolicyValidator: PasswordPolicyValidator?, showPassswordManager: Bool)
}
82 changes: 82 additions & 0 deletions Lock/IconButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// IconButton.swift.swift
//
// Copyright (c) 2017 Auth0 (http://auth0.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import UIKit

class IconButton: UIView {

weak var button: UIButton?

var onPress: (IconButton) -> Void = {_ in }

var color: UIColor = .clear {
didSet {
self.button?.tintColor = self.color
}
}

var icon: UIImage? {
didSet {
self.button?.setImage(self.icon, for: .normal)
}
}

// MARK: - Initialisers
convenience init() {
self.init(frame: CGRect.zero)
}

required override init(frame: CGRect) {
super.init(frame: frame)
self.layoutButton()
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.layoutButton()
}

// MARK: - Layout

private func layoutButton() {
let button = UIButton(type: .custom)
self.addSubview(button)

constraintEqual(anchor: button.topAnchor, toAnchor: self.topAnchor)
constraintEqual(anchor: button.leftAnchor, toAnchor: self.leftAnchor)
constraintEqual(anchor: button.rightAnchor, toAnchor: self.rightAnchor)
constraintEqual(anchor: button.bottomAnchor, toAnchor: self.bottomAnchor)
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
}

func pressed(_ sender: Any) {
self.onPress(self)
}

override var intrinsicContentSize: CGSize {
return CGSize(width: 50, height: UIViewNoIntrinsicMetric)
}
}
24 changes: 23 additions & 1 deletion Lock/InputField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class InputField: UIView, UITextFieldDelegate, Stylable {

private weak var errorLabelTopPadding: NSLayoutConstraint?
private weak var textFieldLeftAnchor: NSLayoutConstraint?
private weak var textFieldRightPadding: NSLayoutConstraint?
private(set) var state: State = .invalid(nil)
private weak var borderColor: UIColor?
private weak var borderColorError: UIColor?
Expand Down Expand Up @@ -163,7 +164,7 @@ class InputField: UIView, UITextFieldDelegate, Stylable {

self.textFieldLeftAnchor = constraintEqual(anchor: textField.leftAnchor, toAnchor: iconContainer.rightAnchor, constant: 16)
constraintEqual(anchor: textField.topAnchor, toAnchor: container.topAnchor)
constraintEqual(anchor: textField.rightAnchor, toAnchor: container.rightAnchor, constant: -16)
self.textFieldRightPadding = constraintEqual(anchor: textField.rightAnchor, toAnchor: container.rightAnchor, constant: -16)
constraintEqual(anchor: textField.bottomAnchor, toAnchor: container.bottomAnchor)
dimension(dimension: textField.heightAnchor, withValue: 50)
textField.translatesAutoresizingMaskIntoConstraints = false
Expand Down Expand Up @@ -199,6 +200,27 @@ class InputField: UIView, UITextFieldDelegate, Stylable {
return CGSize(width: 230, height: 50)
}

// MARK: - Password Manager

func addFieldButton(withIcon name: String, color: UIColor = .black) -> IconButton? {
guard let container = self.containerView, let textField = self.textField else { return nil }

let button = IconButton()
button.icon = LazyImage(name: name, bundle: Lock.bundle).image(compatibleWithTraits: self.traitCollection)
button.color = color
container.addSubview(button)

self.textFieldRightPadding?.isActive = false
constraintEqual(anchor: textField.rightAnchor, toAnchor: button.leftAnchor)
constraintEqual(anchor: button.leftAnchor, toAnchor: textField.rightAnchor)
constraintEqual(anchor: button.topAnchor, toAnchor: textField.topAnchor)
constraintEqual(anchor: button.bottomAnchor, toAnchor: textField.bottomAnchor)
constraintEqual(anchor: button.rightAnchor, toAnchor: container.rightAnchor)
button.translatesAutoresizingMaskIntoConstraints = false

return button
}

// MARK: - Internal

enum State {
Expand Down
2 changes: 2 additions & 0 deletions Lock/Lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ FOUNDATION_EXPORT const unsigned char LockVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <Lock/PublicHeader.h>

#import "OnePasswordExtension.h"


15 changes: 15 additions & 0 deletions Lock/Lock.xcassets/ic_onepassword.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_onepassword.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Binary file not shown.
Loading