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

Fixed single connection behaviour for enterprise #387

Merged
merged 7 commits into from
Feb 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: 2 additions & 2 deletions Lock/AuthPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class AuthPresenter: Presentable, Loggable {

var messagePresenter: MessagePresenter?

init(connections: Connections, interactor: OAuth2Authenticatable, customStyle: [String: AuthStyle]) {
self.connections = connections.oauth2
init(connections: [OAuth2Connection], interactor: OAuth2Authenticatable, customStyle: [String: AuthStyle]) {
self.connections = connections
self.interactor = interactor
self.customStyle = customStyle
}
Expand Down
4 changes: 2 additions & 2 deletions Lock/ConnectionBuildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public protocol ConnectionBuildable: Connections {

/**
Adds a new enterprise connection

- parameter name: name of the connection
- parameter domain: array of enterprise domains
- paramater style: style used when displayed as button
Expand All @@ -75,6 +75,7 @@ public protocol ConnectionBuildable: Connections {
}

public extension ConnectionBuildable {

/**
Configure a database connection

Expand All @@ -87,5 +88,4 @@ public extension ConnectionBuildable {
public mutating func database(name: String, requiresUsername: Bool, usernameValidator: UsernameValidator = UsernameValidator(), passwordPolicy: PasswordPolicy = .none) {
self.database(name: name, requiresUsername: requiresUsername, usernameValidator: usernameValidator, passwordValidator: PasswordPolicyValidator(policy: passwordPolicy))
}

}
2 changes: 1 addition & 1 deletion Lock/Connections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public struct SocialConnection: OAuth2Connection {
public let style: AuthStyle
}

public struct EnterpriseConnection : OAuth2Connection {
public struct EnterpriseConnection: OAuth2Connection {
public let name: String
public let domains: [String]
public let style: AuthStyle
Expand Down
24 changes: 16 additions & 8 deletions Lock/EnterpriseDomainInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,45 @@ import Auth0

struct EnterpriseDomainInteractor: HRDAuthenticatable {

var email: String? = nil
var validEmail: Bool = false
var email: String? {
return self.user.email
}

var validEmail: Bool {
return self.user.validEmail
}

var connection: EnterpriseConnection? = nil
var domain: String? = nil

let user: User
let connections: [EnterpriseConnection]
let emailValidator: InputValidator = EmailValidator()
let authenticator: OAuth2Authenticatable

init(connections: Connections, authentication: OAuth2Authenticatable) {
init(connections: Connections, user: User, authentication: OAuth2Authenticatable) {
self.connections = connections.enterprise
self.authenticator = authentication

if self.connections.count == 1 && connections.oauth2.isEmpty && connections.database == nil {
self.connection = self.connections.first
}
self.user = user
}

func match(domain: String) -> EnterpriseConnection? {
return connections.filter { $0.domains.contains(domain) }.first
}

mutating func updateEmail(_ value: String?) throws {
self.validEmail = false
self.connection = nil

email = value?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if let error = emailValidator.validate(value) {
self.user.email = value?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let error = emailValidator.validate(self.email)
self.user.validEmail = error == nil
if let error = error {
throw error
}
self.validEmail = true

self.connection = nil
if let domain = value?.components(separatedBy: "@").last {
self.connection = match(domain: domain)
Expand Down
39 changes: 2 additions & 37 deletions Lock/EnterpriseDomainPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,12 @@ import Foundation
class EnterpriseDomainPresenter: Presentable, Loggable {

var interactor: EnterpriseDomainInteractor
var customLogger: Logger?
var user: User
var options: Options
var authPresenter: AuthPresenter?

init(interactor: EnterpriseDomainInteractor, navigator: Navigable, user: User, options: Options) {
init(interactor: EnterpriseDomainInteractor, navigator: Navigable, options: Options) {
self.interactor = interactor
self.navigator = navigator
self.user = user
self.options = options
}

Expand All @@ -43,22 +40,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable {
var view: View {
let email = self.interactor.validEmail ? self.interactor.email : nil
let authCollectionView = self.authPresenter?.newViewToEmbed(withInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0), isLogin: true)

if let enterpriseButton = EnterpriseButton(forConnections: interactor.connections, customStyle: [:], isLogin: true, onAction: {
self.interactor.login { error in
Queue.main.async {
if let error = error {
self.messagePresenter?.showError(error)
self.logger.error("Enterprise connection failed: \(error)")
} else {
self.logger.debug("Enterprise authenticator launched")
}
}
}}) {
let view = EnterpriseDomainView(authButton: enterpriseButton, authCollectionView: authCollectionView)
return view
}

let view = EnterpriseDomainView(email: email, authCollectionView: authCollectionView)
let form = view.form

Expand All @@ -70,7 +51,6 @@ class EnterpriseDomainPresenter: Presentable, Loggable {
guard case .email = input.type else { return }
do {
try self.interactor.updateEmail(input.text)
self.user.email = self.interactor.email
input.showValid()
if let connection = self.interactor.connection {
self.logger.debug("Enterprise connection match: \(connection)")
Expand Down Expand Up @@ -113,19 +93,4 @@ class EnterpriseDomainPresenter: Presentable, Loggable {
return view
}

}

func EnterpriseButton(forConnections connections: [EnterpriseConnection], customStyle: [String: AuthStyle], isLogin login: Bool, onAction: @escaping () -> () ) -> AuthButton? {
guard let connection = connections.first, connections.count == 1 else { return nil }
let style = customStyle[connection.name] ?? connection.style
let button = AuthButton(size: .big)
button.title = style.localizedLoginTitle.uppercased()
button.normalColor = style.normalColor
button.highlightedColor = style.highlightedColor
button.titleColor = style.foregroundColor
button.icon = style.image.image(compatibleWithTraits: button.traitCollection)
button.onPress = { _ in
onAction()
}
return button
}
}
88 changes: 57 additions & 31 deletions Lock/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,40 +55,53 @@ struct Router: Navigable {
let interactor = CDNLoaderInteractor(baseURL: self.lock.authentication.url, clientId: self.lock.authentication.clientId)
return ConnectionLoadingPresenter(loader: interactor, navigator: self, dispatcher: lock.observerStore, options: self.lock.options)
}
if let database = connections.database {
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)
let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options)
if !connections.oauth2.isEmpty {

let whitelistForActiveAuth = self.lock.options.enterpriseConnectionUsingActiveAuth
switch (connections.database, connections.oauth2, connections.enterprise) {
// Database root
case (.some(let database), let oauth2, let enterprise):
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)
let presenter = DatabasePresenter(interactor: interactor, connection: database, navigator: self, options: self.lock.options)
if !oauth2.isEmpty {
let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
presenter.authPresenter = AuthPresenter(connections: oauth2, interactor: interactor, customStyle: self.lock.style.oauth2)
}
if !enterprise.isEmpty {
let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
let interactor = EnterpriseDomainInteractor(connections: connections, user: self.user, authentication: authInteractor)
presenter.enterpriseInteractor = interactor
}
return presenter
// Single Enterprise with active auth support (e.g. AD)
case (nil, let oauth2, let enterprise) where oauth2.isEmpty && enterprise.hasJustOne(andIn: whitelistForActiveAuth):
guard let connection = enterprise.first else { return nil }
return enterpriseActiveAuth(connection: connection, domain: connection.domains.first)
// Single Enterprise with support for passive auth only (web auth) and some social connections
case (nil, let oauth2, let enterprise) where enterprise.hasJustOne(andNotIn: whitelistForActiveAuth):
guard let connection = enterprise.first else { return nil }
let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
let connections: [OAuth2Connection] = oauth2 + [connection]
return AuthPresenter(connections: connections, interactor: authInteractor, customStyle: self.lock.style.oauth2)
// Social connections only
case (nil, let oauth2, let enterprise) where enterprise.isEmpty:
let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
presenter.authPresenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: self.lock.style.oauth2)
}
if !connections.enterprise.isEmpty {
let presenter = AuthPresenter(connections: oauth2, interactor: interactor, customStyle: self.lock.style.oauth2)
return presenter
// Multiple enterprise connections and maybe some social
case (nil, let oauth2, let enterprise) where !enterprise.isEmpty:
let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
let interactor = EnterpriseDomainInteractor(connections: connections, authentication: authInteractor)
presenter.enterpriseInteractor = interactor
}
return presenter
}
if !connections.enterprise.isEmpty {
let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
let interactor = EnterpriseDomainInteractor(connections: connections, authentication: authInteractor)
if let connection = interactor.connection, self.lock.options.enterpriseConnectionUsingActiveAuth.contains(connection.name) {
return enterpriseActiveAuth(connection: connection, domain: connection.domains.first)
}
let presenter = EnterpriseDomainPresenter(interactor: interactor, navigator: self, user: self.user, options: self.lock.options)
if !connections.oauth2.isEmpty {
presenter.authPresenter = AuthPresenter(connections: connections, interactor: authInteractor, customStyle: self.lock.style.oauth2)
}
return presenter
}
if !connections.oauth2.isEmpty {
let interactor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options)
let presenter = AuthPresenter(connections: connections, interactor: interactor, customStyle: self.lock.style.oauth2)
return presenter
let interactor = EnterpriseDomainInteractor(connections: connections, user: self.user, authentication: authInteractor)
let presenter = EnterpriseDomainPresenter(interactor: interactor, navigator: self, options: self.lock.options)
if !oauth2.isEmpty {
presenter.authPresenter = AuthPresenter(connections: connections.oauth2, interactor: authInteractor, customStyle: self.lock.style.oauth2)
}
return presenter
// Not supported connections configuration
default:
return nil
}
return nil
}

var forgotPassword: Presentable? {
Expand Down Expand Up @@ -206,3 +219,16 @@ struct Router: Navigable {
}
}
}

private extension Array where Element: OAuth2Connection {
func hasJustOne(andIn list: [String]) -> Bool {
guard let connection = self.first, self.count == 1 else { return false }
return list.contains(connection.name)
}

func hasJustOne(andNotIn list: [String]) -> Bool {
guard let connection = self.first, self.count == 1 else { return false }
return !list.contains(connection.name)
}

}
15 changes: 8 additions & 7 deletions LockTests/Interactors/EnterpriseDomainInteractorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
var credentials: Credentials?
var connections: OfflineConnections!
var enterprise: EnterpriseDomainInteractor!
var user: User!

beforeEach {
user = User()
connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: ["test.com"])
connections.enterprise(name: "validAD", domains: ["valid.com"])
Expand All @@ -45,7 +47,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
var dispatcher = ObserverStore()
dispatcher.onAuth = {credentials = $0}
authentication = Auth0OAuth2Interactor(webAuth: webAuth, dispatcher: dispatcher, options: LockOptions())
enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

afterEach {
Expand All @@ -69,7 +71,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: [])

enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

it("connection should not default to single connection") {
Expand All @@ -87,7 +89,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
beforeEach {
connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: [])
enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

it("should raise no error but no connection provided") {
Expand All @@ -102,7 +104,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
beforeEach {
connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: ["test.com"])
enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

it("should match email domain") {
Expand Down Expand Up @@ -138,7 +140,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: ["test.com", "pepe.com"])

enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

it("should match first email domain and provide enteprise connection") {
Expand All @@ -163,7 +165,7 @@ class EnterpriseDomainInteractorSpec: QuickSpec {

connections = OfflineConnections()
connections.enterprise(name: "TestAD", domains: ["test.com"])
enterprise = EnterpriseDomainInteractor(connections: connections, authentication: authentication)
enterprise = EnterpriseDomainInteractor(connections: connections, user: user, authentication: authentication)
}

it("should fail to launch on no valid connection") {
Expand Down Expand Up @@ -191,7 +193,6 @@ class EnterpriseDomainInteractorSpec: QuickSpec {
expect(credentials).toEventually(equal(expected))
}


}
}
}
Loading