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

Enterprise Connection only authentication #336

Merged
merged 9 commits into from
Nov 8, 2016
Merged
Show file tree
Hide file tree
Changes from 3 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
10 changes: 10 additions & 0 deletions App/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
Expand Down
1 change: 0 additions & 1 deletion App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class ViewController: UIViewController {
withImage: LazyImage(name: "ic_slack")
)
}
.allowedConnections(["github", "instagram", "Username-Password-Authentication", "slack"])
},
actionButton(withTitle: "LOGIN WITH CUSTOM STYLE") {
return Lock
Expand Down
20 changes: 20 additions & 0 deletions Lock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
5B09717C1DC8F229003AA88F /* EnterpriseDomain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B09717B1DC8F229003AA88F /* EnterpriseDomain.swift */; };
5B09717E1DC8F292003AA88F /* EnterpriseDomainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B09717D1DC8F292003AA88F /* EnterpriseDomainInteractor.swift */; };
5B0971801DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B09717F1DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift */; };
5B0971821DC8FAC5003AA88F /* EnterpriseDomainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0971811DC8FAC5003AA88F /* EnterpriseDomainView.swift */; };
5BC6BC0C1DCBDDC9002EA81C /* EnterpriseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC6BC0B1DCBDDC9002EA81C /* EnterpriseView.swift */; };
5F14565A1D5130E80085DF9C /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F1456591D5130E80085DF9C /* Colors.swift */; };
5F14565B1D5237180085DF9C /* LazyImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F92C68E1D50EAC200CCE6C0 /* LazyImage.swift */; };
5F14565C1D5237210085DF9C /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F1456591D5130E80085DF9C /* Colors.swift */; };
Expand Down Expand Up @@ -191,6 +196,11 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
5B09717B1DC8F229003AA88F /* EnterpriseDomain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomain.swift; path = Lock/EnterpriseDomain.swift; sourceTree = SOURCE_ROOT; };
5B09717D1DC8F292003AA88F /* EnterpriseDomainInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomainInteractor.swift; path = Lock/EnterpriseDomainInteractor.swift; sourceTree = SOURCE_ROOT; };
5B09717F1DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomainPresenter.swift; path = Lock/EnterpriseDomainPresenter.swift; sourceTree = SOURCE_ROOT; };
5B0971811DC8FAC5003AA88F /* EnterpriseDomainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseDomainView.swift; path = Lock/EnterpriseDomainView.swift; sourceTree = SOURCE_ROOT; };
5BC6BC0B1DCBDDC9002EA81C /* EnterpriseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EnterpriseView.swift; path = Lock/EnterpriseView.swift; sourceTree = SOURCE_ROOT; };
5F1456591D5130E80085DF9C /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Colors.swift; path = Lock/Colors.swift; sourceTree = SOURCE_ROOT; };
5F14565D1D5237820085DF9C /* DatabaseView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DatabaseView.swift; path = Lock/DatabaseView.swift; sourceTree = SOURCE_ROOT; };
5F1C498D1D8360AA005B74FC /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Style.swift; path = Lock/Style.swift; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -364,9 +374,11 @@
5FC4348E1D1E0A57005188BC /* DatabaseAuthenticable.swift */,
5F508FF61D1D868900EAA650 /* DatabaseInteractor.swift */,
5F73CDD91D30957900D8D8D1 /* DatabasePasswordInteractor.swift */,
5B09717D1DC8F292003AA88F /* EnterpriseDomainInteractor.swift */,
5FBE5CBD1D3E5C7B0038536D /* MultifactorAuthenticatable.swift */,
5FBE5CBF1D3E5E0A0038536D /* MultifactorInteractor.swift */,
5F73CDD71D3093BF00D8D8D1 /* PasswordRecoverable.swift */,
5B09717B1DC8F229003AA88F /* EnterpriseDomain.swift */,
5F57DFD11D4FE59800C54DA8 /* OAuth2Authenticatable.swift */,
5F57DFD31D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift */,
5F2496B21D665A5600A1C6E2 /* DatabaseUserCreator.swift */,
Expand Down Expand Up @@ -402,6 +414,7 @@
children = (
5F1C49921D8360DF005B74FC /* LoadingView.swift */,
5F73CDD31D3073BE00D8D8D1 /* DatabaseForgotPasswordView.swift */,
5B0971811DC8FAC5003AA88F /* EnterpriseDomainView.swift */,
5F50900D1D1DF40400EAA650 /* DatabaseOnlyView.swift */,
5FBE5CB91D3E59B90038536D /* MultifactorCodeView.swift */,
5FC434851D1DF769005188BC /* View.swift */,
Expand Down Expand Up @@ -516,6 +529,7 @@
children = (
5F1C498F1D8360BF005B74FC /* ConnectionLoadingPresenter.swift */,
5F73CDD51D30790500D8D8D1 /* DatabaseForgotPasswordPresenter.swift */,
5B09717F1DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift */,
5FC434891D1DF82A005188BC /* DatabasePresenter.swift */,
5F73CDCF1D30250900D8D8D1 /* MessagePresenter.swift */,
5FBE5CC51D3E7F9D0038536D /* MultifactorPresenter.swift */,
Expand Down Expand Up @@ -613,6 +627,7 @@
5F99AA931D1BABFC00D27842 /* SecondaryButton.swift */,
5F51EE671D1C88FC0024BCD6 /* SignUpView.swift */,
5F51EE691D1CBC830024BCD6 /* SingleInputView.swift */,
5BC6BC0B1DCBDDC9002EA81C /* EnterpriseView.swift */,
5FD6772B1D4C303C004B87C4 /* AuthButton.swift */,
);
path = Components;
Expand Down Expand Up @@ -881,6 +896,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5B09717E1DC8F292003AA88F /* EnterpriseDomainInteractor.swift in Sources */,
5F5F98DA1D22EF490016FC22 /* Router.swift in Sources */,
5F14565A1D5130E80085DF9C /* Colors.swift in Sources */,
5F57DFC91D4F87CE00C54DA8 /* AuthCollectionView.swift in Sources */,
Expand Down Expand Up @@ -910,8 +926,10 @@
5F2496B61D665AA800A1C6E2 /* InputValidationError.swift in Sources */,
5FFC54FE1D37E3F700579581 /* Routes.swift in Sources */,
5F1C49931D8360DF005B74FC /* LoadingView.swift in Sources */,
5B0971801DC8F5C4003AA88F /* EnterpriseDomainPresenter.swift in Sources */,
5FC434861D1DF769005188BC /* View.swift in Sources */,
5FDB41CE1D2C79FD00166B67 /* Operations.swift in Sources */,
5B09717C1DC8F229003AA88F /* EnterpriseDomain.swift in Sources */,
5F51EE681D1C88FC0024BCD6 /* SignUpView.swift in Sources */,
5F57DFD41D4FE64700C54DA8 /* Auth0OAuth2Interactor.swift in Sources */,
5F57DFD21D4FE59800C54DA8 /* OAuth2Authenticatable.swift in Sources */,
Expand All @@ -922,8 +940,10 @@
5F92C68F1D50EAC200CCE6C0 /* LazyImage.swift in Sources */,
5F2496BA1D665AE900A1C6E2 /* DatabaseAuthenticatableError.swift in Sources */,
5F57DFC61D4F79DD00C54DA8 /* AuthStyle.swift in Sources */,
5B0971821DC8FAC5003AA88F /* EnterpriseDomainView.swift in Sources */,
5FBE5CC61D3E7F9D0038536D /* MultifactorPresenter.swift in Sources */,
5FEAE2101D1A5691005C0028 /* HeaderView.swift in Sources */,
5BC6BC0C1DCBDDC9002EA81C /* EnterpriseView.swift in Sources */,
5F99AA8C1D1B3F1300D27842 /* InputField.swift in Sources */,
5FBE5CBA1D3E59B90038536D /* MultifactorCodeView.swift in Sources */,
5F1C49901D8360BF005B74FC /* ConnectionLoadingPresenter.swift in Sources */,
Expand Down
10 changes: 10 additions & 0 deletions Lock/CDNLoaderInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable {
connections.database(name: connection.name, requiresUsername: requiresUsername, usernameValidator: connection.usernameValidation)
}
}
info.enterprise.forEach { strategy in
let enterpriseStrategy = strategy.name
strategy.connections.forEach { connection in
let domain = connection.json["domain"] as! String
let domainAlias = connection.json["domain_aliases"] as! [String]
connections.enterprise(name: connection.name, strategy: enterpriseStrategy , domain: domain, domainAlias: domainAlias)
}
}
info.oauth2.forEach { strategy in
strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) }
}
Expand All @@ -108,6 +116,8 @@ private struct ClientInfo {
var auth0: StrategyInfo? { return strategies.filter({ $0.name == "auth0" }).first }

var oauth2: [StrategyInfo] { return strategies.filter { $0.name != "auth0" && !passwordlessStrategyNames.contains($0.name) && !enterpriseStrategyNames.contains($0.name) } }

var enterprise: [StrategyInfo] { return strategies.filter { $0.name != "auth0" && !passwordlessStrategyNames.contains($0.name) && enterpriseStrategyNames.contains($0.name) } }

let passwordlessStrategyNames = [
"email",
Expand Down
10 changes: 10 additions & 0 deletions Lock/ConnectionBuildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public protocol ConnectionBuildable: Connections {
- seeAlso: AuthStyle
*/
mutating func oauth2(name name: String, style: AuthStyle)

/**
Adds a new enterprise connection

- parameter name: name of the connection
- parameter strategy: name of enterprise strategy for the connection
- parameter domain: primary domain of connection
- parameter domainAlias: array of domaina aliases
*/
mutating func enterprise(name name: String, strategy: String, domain: String, domainAlias: [String])
Copy link
Member

Choose a reason for hiding this comment

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

Let's coalesce into a single domain parameter of type [String]. And make sure when we parse the info from the CDN we put the domain one first.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense, I was focusing on AD which is just a comma list in dashboard that presents then first domain in the list as the primary domain. Will change.

}

public extension ConnectionBuildable {
Expand Down
2 changes: 1 addition & 1 deletion Lock/ConnectionLoadingPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ class ConnectionLoadingPresenter: Presentable, Loggable {
}
return LoadingView()
}
}
}
15 changes: 15 additions & 0 deletions Lock/Connections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Foundation
public protocol Connections {
var database: DatabaseConnection? { get }
var oauth2: [OAuth2Connection] { get }
var enterprise: [EnterpriseConnection] {get}

var isEmpty: Bool { get }

Expand Down Expand Up @@ -59,3 +60,17 @@ public struct SocialConnection: OAuth2Connection {
public let name: String
public let style: AuthStyle
}

public protocol EnterpriseConnection {
Copy link
Member

Choose a reason for hiding this comment

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

I think we don't need this protocol now right? or it has some use I don't see?

var name: String { get }
var strategy: String { get }
Copy link
Member

Choose a reason for hiding this comment

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

What's the use of strategy?

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought it might be useful to know the enterprise strategy, if AD present this view etc

var domain: String { get }
var domainAlias: [String] { get }
}

public struct EnterpriseConnector : EnterpriseConnection {
public let name: String
public let strategy: String
public let domain: String
public let domainAlias: [String]
}
6 changes: 3 additions & 3 deletions Lock/DatabasePasswordInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ struct DatabasePasswordInteractor: PasswordRecoverable {
func requestEmail(callback: (PasswordRecoverableError?) -> ()) {
guard let email = self.email else { return callback(.NonValidInput) }
guard let connection = self.connections.database?.name else { return callback(.NoDatabaseConnection) }

self.authentication
.resetPassword(email: email, connection: connection)
.start {
guard case .Success = $0 else { return callback(.EmailNotSent) }
callback(nil)
}
}
}
}
}
33 changes: 33 additions & 0 deletions Lock/EnterpriseDomain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// EnterpriseDomain.swift
//
// Copyright (c) 2016 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 Foundation

protocol EnterpriseDomain {
Copy link
Member

Choose a reason for hiding this comment

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

Let's rename this to HRDAuthenticatable

var email: String? { get }
var validEmail: Bool { get }
var validDomain: Bool { get }

mutating func updateEmail(value: String?) throws

func requestConnection(callback: (OAuth2AuthenticatableError?) -> ())
}
71 changes: 71 additions & 0 deletions Lock/EnterpriseDomainInteractor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// EnterpriseDomainInteractor.swift
//
// Copyright (c) 2016 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 Foundation
import Auth0

struct EnterpriseDomainInteractor: EnterpriseDomain {

var email: String? = nil
var validEmail: Bool = false
var validDomain: Bool = false

let connections: Connections
let emailValidator: InputValidator = EmailValidator()
let domainValidator: EnterpriseDomainValidator
let authenticator: OAuth2Authenticatable

init(connections: Connections, auth: OAuth2Authenticatable) {
self.connections = connections
self.domainValidator = EnterpriseDomainValidator(connections: connections.enterprise)
self.authenticator = auth
}

mutating func updateEmail(value: String?) throws {

validEmail = false
validDomain = false

// Validate email
email = value?.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
if let error = emailValidator.validate(value) {
throw error
}
validEmail = true

// Validate Enterprise domain
if let error = domainValidator.validate(self.email) {
throw error
}
validDomain = true
}

func requestConnection(callback: (OAuth2AuthenticatableError?) -> ()) {
guard let _ = self.email else { return callback(.NoConnectionAvailable) }
Copy link
Member

Choose a reason for hiding this comment

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

probably best to use self.email != nil

Copy link
Member Author

Choose a reason for hiding this comment

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

It feels less swifty in comparison to guard. However, I also do not like using let _ to suppress warning.

guard let connection = self.domainValidator.enterpriseConnection else { return callback(.NoConnectionAvailable) }

authenticator.login(connection.name) { error in
return callback(error)
}
callback(nil)
Copy link
Member

Choose a reason for hiding this comment

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

Is this right?, shouldn't it be inside of the callback?. Maybe just login like

authenticator.login(connection.name, callback);

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll try this it's cleaner.

}
}
78 changes: 78 additions & 0 deletions Lock/EnterpriseDomainPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// EnterpriseDomainPresenter.swift
//
// Copyright (c) 2016 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 Foundation

class EnterpriseDomainPresenter: Presentable, Loggable {

var interactor: EnterpriseDomainInteractor
var customLogger: Logger?

init(interactor: EnterpriseDomainInteractor) {
self.interactor = interactor
}

var messagePresenter: MessagePresenter?

var view: View {
let email = self.interactor.validEmail ? self.interactor.email : nil
let view = EnterpriseDomainView(email: email)
let form = view.form
view.form?.onValueChange = { input in
self.messagePresenter?.hideCurrent()

guard case .Email = input.type else { return }
do {
try self.interactor.updateEmail(input.text)
input.showValid()
} catch {
input.showError()
}
}

let action = { (button: PrimaryButton) in
self.messagePresenter?.hideCurrent()
self.logger.info("Enterprise connection validation: \(self.interactor.email)")
let interactor = self.interactor
button.inProgress = true
interactor.requestConnection { error in
Queue.main.async {
button.inProgress = false
form?.needsToUpdateState()
if let error = error {
self.messagePresenter?.showError(error)
self.logger.error("Failed: \(error)")
} else {
self.logger.debug("Launch Web Auth")
}
}

}
}
view.primaryButton?.onPress = action
view.form?.onReturn = {_ in
guard let button = view.primaryButton else { return }
action(button)
}
return view
}
}
Loading