Skip to content

Commit

Permalink
Merge pull request #386 from auth0/feature_connection_scopes
Browse files Browse the repository at this point in the history
Added Connection Scope support for OAuth2 connections
  • Loading branch information
hzalaz authored Mar 13, 2017
2 parents 4f8156c + 3b04d11 commit 3305cfe
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 46 deletions.
6 changes: 5 additions & 1 deletion App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ class ViewController: UIViewController {
.classic()
.withOptions {
applyDefaultOptions(&$0)
$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))
]
}
.withStyle {
$0.oauth2["slack"] = AuthStyle(
name: "Slack",
color: UIColor ( red: 0.4118, green: 0.8078, blue: 0.6588, alpha: 1.0 ),
withImage: LazyImage(name: "ic_slack")
)
}
}
},
actionButton(withTitle: "LOGIN WITH CUSTOM STYLE") {
return Lock
Expand Down
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "auth0/Auth0.swift" ~> 1.2
github "auth0/Auth0.swift" ~>1.3.0
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
github "auth0/Auth0.swift" "1.2.0"
github "auth0/Auth0.swift" "1.3.0"
github "emaloney/CleanroomASL" "2.1.2"
github "Quick/Nimble" "v5.1.1"
github "AliSoftware/OHHTTPStubs" "5.2.2-swift3"
Expand Down
8 changes: 4 additions & 4 deletions Lock.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
5B0CF2DE1DE9B4AF00F82BF4 /* DatabaseUserCreatorErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0CF2DC1DE9B47C00F82BF4 /* DatabaseUserCreatorErrorSpec.swift */; };
5B2826431E32297E00E48467 /* NativeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2826421E32297E00E48467 /* NativeHandler.swift */; };
5B2981781DD51A460062535C /* InfoBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2981771DD51A460062535C /* InfoBarView.swift */; };
5B4177431E44827B007843EA /* Auth0OAuth2InteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F92C68A1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift */; };
5B3DA2561E6ED92F00370C17 /* Auth0OAuth2InteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F92C68A1D4FE90F00CCE6C0 /* Auth0OAuth2InteractorSpec.swift */; };
5B4DE0151DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0141DD66DE1004C8AC2 /* EnterpriseDomainInteractor.swift */; };
5B4DE0171DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0161DD67064004C8AC2 /* EnterpriseActiveAuthPresenter.swift */; };
5B4DE0191DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4DE0181DD670F7004C8AC2 /* EnterpriseActiveAuthView.swift */; };
Expand All @@ -25,7 +25,7 @@
5B55F3CB1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3CA1E242A2E00B75CF5 /* UnrecoverableErrorPresenter.swift */; };
5B55F3D11E244B8700B75CF5 /* UnrecoverableError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D01E244B8700B75CF5 /* UnrecoverableError.swift */; };
5B55F3D41E24FFD000B75CF5 /* UnrecoverableErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B55F3D21E24F3F000B75CF5 /* UnrecoverableErrorSpec.swift */; };
5B6631531DDB9B28001CB043 /* EnterpriseDomainInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */; };
5B889AEC1E6EE8C400C9FBAF /* EnterpriseDomainInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6631511DDB990B001CB043 /* EnterpriseDomainInteractorSpec.swift */; };
5BA563F11DD117550002D3AB /* EnterpriseActiveAuthInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA563EF1DD1171F0002D3AB /* EnterpriseActiveAuthInteractorSpec.swift */; };
5BB4A7C11DF9A38E008E8C37 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB4A7C01DF9A38E008E8C37 /* DatabaseView.swift */; };
5BCDE1361DDDF17F00AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BCDE1341DDDF12100AA2A6C /* EnterpriseActiveAuthPresenterSpec.swift */; };
Expand Down Expand Up @@ -1055,11 +1055,12 @@
5F6C01551D91656100198ACD /* UsernameValidatorSpec.swift in Sources */,
5F2496BE1D67ADB300A1C6E2 /* EmailValidatorSpec.swift in Sources */,
5B0CF2D61DE9AE0F00F82BF4 /* InputValidationErrorSpec.swift in Sources */,
5B4177431E44827B007843EA /* Auth0OAuth2InteractorSpec.swift in Sources */,
5B3DA2561E6ED92F00370C17 /* Auth0OAuth2InteractorSpec.swift in Sources */,
5FBE5CCA1D3EA1380038536D /* MultifactorPresenterSpec.swift in Sources */,
5BA563F11DD117550002D3AB /* EnterpriseActiveAuthInteractorSpec.swift in Sources */,
5F92C68D1D50E47100CCE6C0 /* AuthStyleSpec.swift in Sources */,
5F5090081D1DE7BA00EAA650 /* NetworkStub.swift in Sources */,
5B889AEC1E6EE8C400C9FBAF /* EnterpriseDomainInteractorSpec.swift in Sources */,
5F0FCF921E201CF300E3D53B /* ObserverStoreSpec.swift in Sources */,
5B55F3D41E24FFD000B75CF5 /* UnrecoverableErrorSpec.swift in Sources */,
5F73CDDC1D309BE900D8D8D1 /* DatabasePasswordInteractorSpec.swift in Sources */,
Expand All @@ -1084,7 +1085,6 @@
5F50900A1D1DEE9A00EAA650 /* Constants.swift in Sources */,
5FE50DBD1D79B8AD00D82290 /* CDNLoaderInteractorSpec.swift in Sources */,
5F390E8D1D63B99300FC549C /* LoggerSpec.swift in Sources */,
5B6631531DDB9B28001CB043 /* EnterpriseDomainInteractorSpec.swift in Sources */,
5B0CF2DE1DE9B4AF00F82BF4 /* DatabaseUserCreatorErrorSpec.swift in Sources */,
5F2037C21D5D02880005D2E2 /* Matchers.swift in Sources */,
5FEEE81E1DB84BBB00B4DFED /* PasswordPolicySpec.swift in Sources */,
Expand Down
12 changes: 9 additions & 3 deletions Lock/Auth0OAuth2Interactor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Auth0

struct Auth0OAuth2Interactor: OAuth2Authenticatable {

let webAuth: Auth0.WebAuth
let authentication: Authentication
let dispatcher: Dispatcher
let options: Options
let nativeHandlers: [String: AuthProvider]
Expand All @@ -41,15 +41,21 @@ struct Auth0OAuth2Interactor: OAuth2Authenticatable {
private func webAuth(withConnection connection: String, callback: @escaping (OAuth2AuthenticatableError?) -> Void) {
var parameters: [String: String] = [:]
self.options.parameters.forEach { parameters[$0] = "\($1)" }
var auth = self.webAuth
.connection(connection)

var auth = authentication.webAuth(withConnection: connection)
.scope(self.options.scope)
.parameters(parameters)

auth = auth.logging(enabled: self.options.logHttpRequest)

if let audience = self.options.audience {
auth = auth.audience(audience)
}

if let connectionScope = self.options.connectionScope[connection] {
auth = auth.connectionScope(connectionScope)
}

auth
.start { result in
switch result {
Expand Down
1 change: 1 addition & 0 deletions Lock/LockOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ struct LockOptions: OptionBuildable {
var loggerOutput: LoggerOutput?
var logHttpRequest: Bool = false
var scope: String = "openid"
var connectionScope: [String: String] = [:]
var parameters: [String : Any] = [:]
var allow: DatabaseMode = [.Login, .Signup, .ResetPassword]
var autoClose: Bool = true
Expand Down
3 changes: 3 additions & 0 deletions Lock/OptionBuildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public protocol OptionBuildable: Options {
/// Scope used for authentication. By default is `openid`.
var scope: String { get set }

/// Allows you to specify provider scopes for oauth2/social connections with a comma separated list (values depend on the social IdP). By default is empty.
var connectionScope: [String: String] { get set }

/// Authentication parameters sent with every authentication requests. By default is an empty dictionary.
var parameters: [String: Any] { get set }

Expand Down
1 change: 1 addition & 0 deletions Lock/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public protocol Options {
var logHttpRequest: Bool { get }

var scope: String { get }
var connectionScope: [String: String] { get }
var parameters: [String: Any] { get }
var allow: DatabaseMode { get }
var autoClose: Bool { get }
Expand Down
10 changes: 5 additions & 5 deletions Lock/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ struct Router: Navigable {
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, nativeHandlers: self.lock.nativeHandlers)
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)
}
if !enterprise.isEmpty {
let authInteractor = Auth0OAuth2Interactor(webAuth: self.lock.webAuth, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
let authInteractor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
let interactor = EnterpriseDomainInteractor(connections: connections, user: self.user, authentication: authInteractor)
presenter.enterpriseInteractor = interactor
}
Expand All @@ -80,17 +80,17 @@ struct Router: Navigable {
// 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, nativeHandlers: self.lock.nativeHandlers)
let authInteractor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
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, nativeHandlers: self.lock.nativeHandlers)
let interactor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
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, nativeHandlers: self.lock.nativeHandlers)
let authInteractor = Auth0OAuth2Interactor(authentication: self.lock.authentication, dispatcher: lock.observerStore, options: self.lock.options, nativeHandlers: self.lock.nativeHandlers)
let interactor = EnterpriseDomainInteractor(connections: connections, user: self.user, authentication: authInteractor)
let presenter = EnterpriseDomainPresenter(interactor: interactor, navigator: self, options: self.lock.options)
if !oauth2.isEmpty {
Expand Down
48 changes: 32 additions & 16 deletions LockTests/Interactors/Auth0OAuth2InteractorSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ class Auth0OAuth2InteractorSpec: QuickSpec {

override func spec() {

let authentication = Auth0.authentication(clientId: clientId, domain: domain)
let authentication = MockAuthentication(clientId: clientId, domain: domain)

var webAuth: MockWebAuth!
var options: LockOptions!
var credentials: Credentials?
var nativeHandlers: [String: AuthProvider] = [:]
Expand All @@ -49,13 +48,12 @@ class Auth0OAuth2InteractorSpec: QuickSpec {

beforeEach {
credentials = nil
webAuth = MockWebAuth()
options = LockOptions()
authHandler = MockNativeAuthHandler()
authHandler.authentication = authentication
dispatcher = ObserverStore()
dispatcher.onAuth = { credentials = $0 }
interactor = Auth0OAuth2Interactor(webAuth: webAuth, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
}

afterEach {
Expand All @@ -72,56 +70,74 @@ class Auth0OAuth2InteractorSpec: QuickSpec {

it("should set connection") {
interactor.login("facebook", callback: { _ in })
expect(webAuth.connection) == "facebook"
expect(authentication.webAuth.connection) == "facebook"
}

it("should set scope") {
interactor.login("facebook", callback: { _ in })
expect(webAuth.scope) == "openid"
expect(authentication.webAuth.scope) == "openid"
}

it("should set connection scope for specified connection") {
options.connectionScope = ["facebook": "user_friends,email"]
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor.login("facebook", callback: { _ in })
expect(authentication.webAuth.parameters["connection_scope"]) == "user_friends,email"
}

it("should set connection scope for matching connections only") {
options.connectionScope = ["facebook": "user_friends,email",
"google-oauth2": "gmail,ads"]
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor.login("facebook", callback: { _ in })
expect(authentication.webAuth.parameters["connection_scope"]) == "user_friends,email"
interactor.login("google-oauth2", callback: { _ in })
expect(authentication.webAuth.parameters["connection_scope"]) == "gmail,ads"
}

it("should not set audience if nil") {
options.audience = nil
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor.login("facebook", callback: { _ in })
expect(webAuth.audience).to(beNil())
expect(authentication.webAuth.audience).to(beNil())
}

it("should set audience") {
options.audience = "https://myapi.com/v1"
interactor = Auth0OAuth2Interactor(webAuth: webAuth, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor.login("facebook", callback: { _ in })
expect(webAuth.audience) == "https://myapi.com/v1"
expect(authentication.webAuth.audience) == "https://myapi.com/v1"
}

it("should set parameters") {
let state = UUID().uuidString
options.parameters = ["state": state as Any]
interactor = Auth0OAuth2Interactor(webAuth: webAuth, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor.login("facebook", callback: { _ in })
expect(webAuth.params["state"]) == state
expect(authentication.webAuth.parameters["state"]) == state
}

it("should not yield error on success") {
webAuth.result = { return .success(result: mockCredentials()) }
authentication.webAuthResult = { return .success(result: mockCredentials()) }
interactor.login("facebook") { error = $0 }
expect(error).toEventually(beNil())
}

it("should call credentials callback") {
let expected = mockCredentials()
webAuth.result = { return .success(result: expected) }
authentication.webAuthResult = { return .success(result: expected) }
interactor.login("facebook") { error = $0 }
expect(credentials).toEventually(equal(expected))
}

it("should handle cancel error") {
webAuth.result = { return .failure(error: WebAuthError.userCancelled) }
authentication.webAuthResult = { return .failure(error: WebAuthError.userCancelled) }
interactor.login("facebook") { error = $0 }
expect(error).toEventually(equal(OAuth2AuthenticatableError.cancelled))
}

it("should handle generic error") {
webAuth.result = { return .failure(error: WebAuthError.noBundleIdentifierFound) }
authentication.webAuthResult = { return .failure(error: WebAuthError.noBundleIdentifierFound) }
interactor.login("facebook") { error = $0 }
expect(error).toEventually(equal(OAuth2AuthenticatableError.couldNotAuthenticate))
}
Expand All @@ -133,7 +149,7 @@ class Auth0OAuth2InteractorSpec: QuickSpec {
beforeEach {
nativeHandlers.removeAll()
nativeHandlers["facebook"] = authHandler
interactor = Auth0OAuth2Interactor(webAuth: webAuth, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
interactor = Auth0OAuth2Interactor(authentication: authentication, dispatcher: dispatcher, options: options, nativeHandlers: nativeHandlers)
}

afterEach {
Expand Down
Loading

0 comments on commit 3305cfe

Please sign in to comment.