Skip to content

Commit

Permalink
Added Password Manager Tests
Browse files Browse the repository at this point in the history
Update README PaswordManager Section
  • Loading branch information
cocojoe committed Apr 7, 2017
1 parent 3dfeae7 commit d3468ad
Show file tree
Hide file tree
Showing 19 changed files with 341 additions and 82 deletions.
1 change: 0 additions & 1 deletion App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class ViewController: UIViewController {
.classic()
.withOptions {
applyDefaultOptions(&$0)
$0.enableOnePasswordWithIdentifier = "com.auth0.Lock"
$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
55 changes: 40 additions & 15 deletions Lock.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Lock/ClassicRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ struct ClassicRouter: Router {
let authentication = self.lock.authentication
let interactor = DatabaseInteractor(connection: database, authentication: authentication, user: self.user, options: self.lock.options, dispatcher: lock.observerStore)
let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options)
if let passwordIdentifier = self.lock.options.enableOnePasswordWithIdentifier, OnePassword.isAvailable() {
let passwordManager = OnePassword(identifier: passwordIdentifier, controller: self.controller)
if self.lock.options.showPasswordManager, OnePassword.isAvailable() {
let passwordManager = OnePassword(identifier: self.lock.options.passwordManagerAppIdentifier, controller: self.controller)
presenter.passwordManager = passwordManager
}
if !oauth2.isEmpty {
Expand Down
4 changes: 2 additions & 2 deletions Lock/DatabasePresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class DatabasePresenter: Presentable, Loggable {

if let passwordManager = self.passwordManager, let passwordButton = view.passwordManagerButton {
passwordButton.onPress = { _ in
passwordManager.login { result, error in
passwordManager.login { error, result in
guard error == nil else {
return self.logger.error("There was a problem with the password manager: \(error)")
}
Expand Down Expand Up @@ -221,7 +221,7 @@ class DatabasePresenter: Presentable, Loggable {

if let passwordManager = self.passwordManager, let passwordButton = view.passwordManagerButton {
passwordButton.onPress = { _ in
passwordManager.store(withPolicy: passwordPolicyValidator?.policy.onePasswordRules()) { result, error in
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)")
}
Expand Down
4 changes: 2 additions & 2 deletions Lock/IconButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ class IconButton: UIView {

self.addSubview(button)

constraintEqual(anchor: button.centerXAnchor, toAnchor: self.centerXAnchor)
constraintEqual(anchor: button.topAnchor, toAnchor: self.topAnchor)
constraintEqual(anchor: button.leftAnchor, toAnchor: self.leftAnchor)
constraintEqual(anchor: button.rightAnchor, toAnchor: self.rightAnchor)
constraintEqual(anchor: button.centerYAnchor, toAnchor: self.centerYAnchor)
constraintEqual(anchor: button.bottomAnchor, toAnchor: self.bottomAnchor)
button.translatesAutoresizingMaskIntoConstraints = false

button.addTarget(self, action: #selector(pressed), for: .touchUpInside)
Expand Down
5 changes: 3 additions & 2 deletions Lock/InputField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,14 @@ 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
constraintEqual(anchor: button.leftAnchor, toAnchor: textField.rightAnchor, constant: 0)
constraintEqual(anchor: button.leftAnchor, toAnchor: textField.rightAnchor)
constraintEqual(anchor: button.topAnchor, toAnchor: textField.topAnchor)
constraintEqual(anchor: button.rightAnchor, toAnchor: container.rightAnchor, constant: 0)
constraintEqual(anchor: button.bottomAnchor, toAnchor: textField.bottomAnchor)
constraintEqual(anchor: button.rightAnchor, toAnchor: container.rightAnchor)
button.translatesAutoresizingMaskIntoConstraints = false

return button
Expand Down
3 changes: 2 additions & 1 deletion Lock/LockOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ struct LockOptions: OptionBuildable {

var passwordlessMethod: PasswordlessMethod = .code

var enableOnePasswordWithIdentifier: String?
var showPasswordManager: Bool = true
var passwordManagerAppIdentifier: String = Bundle.main.bundleIdentifier!
}
22 changes: 11 additions & 11 deletions Lock/OnePassword.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ protocol PasswordManager {
var fields: [String: InputField] { get set }

static func isAvailable() -> Bool
func login(callback: @escaping ([String: InputField]?, Error?) -> Void)
func store(withPolicy policy: [String: Any]?, callback: @escaping ([String: InputField]?, Error?) -> Void)
func login(callback: @escaping (Error?, [String: InputField]?) -> Void)
func store(withPolicy policy: [String: Any]?, callback: @escaping (Error?, [String: InputField]?) -> Void)
}

class OnePassword: PasswordManager {
Expand All @@ -36,7 +36,7 @@ class OnePassword: PasswordManager {
weak var controller: UIViewController?
var fields: [String: InputField] = [:]

init(identifier: String, controller: UIViewController?) {
required init(identifier: String, controller: UIViewController?) {
self.identifier = identifier
self.controller = controller
}
Expand All @@ -45,29 +45,29 @@ class OnePassword: PasswordManager {
return LockOnePasswordExtension.shared().isAppExtensionAvailable()
}

func login(callback: @escaping ([String: InputField]?, Error?) -> Void) {
func login(callback: @escaping (Error?, [String: InputField]?) -> Void) {
guard let controller = self.controller else { return }
LockOnePasswordExtension.shared().findLogin(forURLString: self.identifier, for: controller, sender: nil) { (result, error) in
guard error == nil else {
return callback(nil, error)
return callback(error, nil)
}
self.fields[AppExtensionUsernameKey]?.text = result?[AppExtensionUsernameKey] as? String
self.fields[AppExtensionPasswordKey]?.text = result?[AppExtensionPasswordKey] as? String
callback(self.fields, nil)
callback(nil, self.fields)
}
}

func store(withPolicy policy: [String: Any]?, callback: @escaping ([String: InputField]?, Error?) -> Void) {
guard let controller = self.controller else { return }
var loginDetails: [String: String] = [:]
func store(withPolicy policy: [String: Any]?, callback: @escaping (Error?, [String: InputField]?) -> Void) {
guard let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String, let controller = self.controller else { return }
var loginDetails: [String: String] = [ AppExtensionTitleKey: displayName ]
self.fields.forEach { loginDetails[$0] = $1.text }
LockOnePasswordExtension.shared().storeLogin(forURLString: self.identifier, loginDetails: loginDetails, passwordGenerationOptions: policy, for: controller, sender: nil) { (result, error) in
guard error == nil else {
return callback(nil, error)
return callback(error, nil)
}
self.fields[AppExtensionUsernameKey]?.text = result?[AppExtensionUsernameKey] as? String
self.fields[AppExtensionPasswordKey]?.text = result?[AppExtensionPasswordKey] as? String
callback(self.fields, nil)
callback(nil, self.fields)
}
}
}
7 changes: 5 additions & 2 deletions Lock/OptionBuildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,11 @@ public protocol OptionBuildable: Options {
/// Specify the passwordless method, send a passcode or magic link. By default is .code
var passwordlessMethod: PasswordlessMethod { get set }

/// Enable and specify the 1Password identifier to use, typically you will want to use the app's bundle identifier. However you may have a website and want to ensure 1Password works with both by using an identifier like `www.mysite.com`
var enableOnePasswordWithIdentifier: String? { get set }
/// Show password manager button when user credentials are needed, e.g. during Sign Up, and allow storing/retrieving credentials from the password manager. By default is true
var showPasswordManager: Bool { get set }

/// Application identifier used by the password manager, if you also have a web app we recommend adding your site url. By default is the application bundle identifier
var passwordManagerAppIdentifier: String { get set }
}

extension OptionBuildable {
Expand Down
3 changes: 2 additions & 1 deletion Lock/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ public protocol Options {

var passwordlessMethod: PasswordlessMethod { get }

var enableOnePasswordWithIdentifier: String? { get }
var showPasswordManager: Bool { get }
var passwordManagerAppIdentifier: String { get }
}
23 changes: 5 additions & 18 deletions Lock/PasswordPolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,10 @@ public struct PasswordPolicy {

extension PasswordPolicy {
func onePasswordRules() -> [String: Any] {
switch self.name {
case Auth0.low.rawValue:
return [ AppExtensionGeneratedPasswordMinLengthKey: "6" ]
case Auth0.fair.rawValue:
return [ AppExtensionGeneratedPasswordMinLengthKey: "8",
AppExtensionGeneratedPasswordRequireDigitsKey: true ]
case Auth0.good.rawValue:
return [ AppExtensionGeneratedPasswordMinLengthKey: "8",
AppExtensionGeneratedPasswordRequireDigitsKey: true,
AppExtensionGeneratedPasswordRequireSymbolsKey: true ]
case Auth0.excellent.rawValue:
return [ AppExtensionGeneratedPasswordMinLengthKey: "10",
AppExtensionGeneratedPasswordMaxLengthKey: "128",
AppExtensionGeneratedPasswordRequireDigitsKey: true,
AppExtensionGeneratedPasswordRequireSymbolsKey: true ]
default:
return [ AppExtensionGeneratedPasswordMinLengthKey: "1" ]
}
// Excellent
return [ AppExtensionGeneratedPasswordMinLengthKey: "10",
AppExtensionGeneratedPasswordMaxLengthKey: "128",
AppExtensionGeneratedPasswordRequireDigitsKey: true,
AppExtensionGeneratedPasswordRequireSymbolsKey: true ]
}
}
30 changes: 30 additions & 0 deletions LockTests/Models/PasswordPolicySpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ class PasswordPolicySpec: QuickSpec {

}

describe("one password extension") {

it("none") {
let policy = PasswordPolicy.none
expect(policy.onePasswordRules()).to(haveCount(4))
}

it("low") {
let policy = PasswordPolicy.low
expect(policy.onePasswordRules()).to(haveCount(4))
}

it("fair") {
let policy = PasswordPolicy.fair
expect(policy.onePasswordRules()).to(haveCount(4))
}

it("good") {
let policy = PasswordPolicy.good
expect(policy.onePasswordRules()).to(haveCount(4))
}

it("excellent") {
let policy = PasswordPolicy.excellent
expect(policy.onePasswordRules()).to(haveCount(4))
}

}


}
}

Expand Down
83 changes: 83 additions & 0 deletions LockTests/OnePasswordSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// OnePasswordSpec.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 Quick
import Nimble

@testable import Lock

class OnePasswordSpec: QuickSpec {

override func spec() {

var onePassword: OnePassword?
var viewController: MockViewController?

describe("availability") {

it("should not be available without 1password installed") {
expect(OnePassword.isAvailable()) == false
}

}

describe("init") {

beforeEach {
onePassword = nil
viewController = MockViewController()
}

it("should init with identifier") {
onePassword = OnePassword(identifier: "www.mysite.com", controller: nil)
expect(onePassword?.identifier) == "www.mysite.com"
}

it("should init with identifier and controller") {
onePassword = OnePassword(identifier: "www.mysite.com", controller: viewController)
expect(onePassword?.identifier) == "www.mysite.com"
expect(onePassword?.controller).to(equal(viewController))
}

}

describe("action") {

beforeEach {
viewController = MockViewController()
onePassword = OnePassword(identifier: "www.mysite.com", controller: viewController)
}

it("should present extension prompt on login") {
onePassword?.login { _ in }
expect(viewController?.presented).toNot(beNil())
}


it("should present extension prompt on store") {
onePassword?.store(withPolicy: nil) { _ in }
expect(viewController?.presented).toNot(beNil())
}

}
}
}
8 changes: 8 additions & 0 deletions LockTests/OptionsSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ class OptionsSpec: QuickSpec {
expect(options.passwordlessMethod).to(equal(PasswordlessMethod.code))
}

it("should show passwordManager") {
expect(options.showPasswordManager) == true
}

it("should match bundler identifier") {
expect(options.passwordManagerAppIdentifier) == Bundle.main.bundleIdentifier
}

}

describe("validation") {
Expand Down
1 change: 0 additions & 1 deletion LockTests/PasswordPolicyValidatorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class PasswordPolicyValidatorSpec: QuickSpec {
fail("Invalid error. Expected PasswordPolicyViolation")
}
}

}
}

Expand Down
Loading

0 comments on commit d3468ad

Please sign in to comment.