Skip to content

Commit

Permalink
Merge pull request #310 from auth0/feature-dynamic-connections
Browse files Browse the repository at this point in the history
Load connections from Auth0
  • Loading branch information
hzalaz authored Sep 5, 2016
2 parents da66725 + 337d003 commit 4a03274
Show file tree
Hide file tree
Showing 70 changed files with 1,542 additions and 249 deletions.
17 changes: 13 additions & 4 deletions App/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,20 @@ class ViewController: UIViewController {
])
header.translatesAutoresizingMaskIntoConstraints = false

let cdnLoading = AuthButton(size: .Big)
cdnLoading.title = "LOGIN WITH CDN"
cdnLoading.onPress = { [weak self] _ in
let lock = Lock
.classic()
.allowedConnections(["github", "instagram", "Username-Password-Authentication"])
self?.showLock(lock)
}
let databaseOnly = AuthButton(size: .Big)
databaseOnly.title = "LOGIN WITH DB"
databaseOnly.onPress = { [weak self] _ in
let lock = Lock
.classic()
.connections { connections in
.withConnections { connections in
connections.database(name: "Username-Password-Authentication", requiresUsername: true)
}
self?.showLock(lock)
Expand All @@ -59,7 +67,7 @@ class ViewController: UIViewController {
databaseAndSocial.onPress = { [weak self] _ in
let lock = Lock
.classic()
.connections { connections in
.withConnections { connections in
connections.social(name: "facebook", style: .Facebook)
connections.social(name: "google-oauth2", style: .Google)
connections.database(name: "Username-Password-Authentication", requiresUsername: true)
Expand All @@ -71,7 +79,8 @@ class ViewController: UIViewController {
socialOnly.onPress = { [weak self] _ in
let lock = Lock
.classic()
.connections { connections in
.allowedConnections(["facebook", "google-oauth2", "twitter", "dropbox", "bitbucket"])
.withConnections { connections in
connections.social(name: "facebook", style: .Facebook)
connections.social(name: "google-oauth2", style: .Google)
connections.social(name: "instagram", style: .Instagram)
Expand All @@ -83,7 +92,7 @@ class ViewController: UIViewController {
self?.showLock(lock)
}

let stack = UIStackView(arrangedSubviews: [wrap(databaseOnly), wrap(socialOnly), wrap(databaseAndSocial)])
let stack = UIStackView(arrangedSubviews: [wrap(cdnLoading), wrap(databaseOnly), wrap(socialOnly), wrap(databaseAndSocial)])
stack.axis = .Vertical
stack.distribution = .FillProportionally
stack.alignment = .Fill
Expand Down
160 changes: 160 additions & 0 deletions CDNLoaderInteractor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// CDNLoaderInteractor.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

typealias JSONObject = [String: AnyObject]
typealias JSONArray = [JSONObject]

struct CDNLoaderInteractor: RemoteConnectionLoader, Loggable {

let url: NSURL

init(baseURL: NSURL, clientId: String) {
self.url = NSURL(string: "client/\(clientId).js", relativeToURL: cdnURL(from: baseURL))!
}

func load(callback: Connections? -> ()) {
self.logger.info("Loading client info from \(self.url)")
let task = NSURLSession.sharedSession().dataTaskWithURL(self.url) { (data, response, error) in
guard error == nil else {
self.logger.error("Failed to load with error \(error!)")
callback(nil)
return
}
guard let response = response as? NSHTTPURLResponse else {
self.logger.error("Response was not NSHTTURLResponse")
return callback(nil)
}

let payload: String?
if let data = data {
payload = String(data: data, encoding: NSUTF8StringEncoding)
} else {
payload = nil
}
guard 200...299 ~= response.statusCode else {
self.logger.error("HTTP response was not successful. HTTP \(response.statusCode) <\(payload ?? "No Body")>")
return callback(nil)
}

guard var jsonp = payload else {
self.logger.error("HTTP response had no jsonp \(payload ?? "No Body")")
return callback(nil)
}

self.logger.verbose("Received jsonp \(jsonp)")

if let prefixRange = jsonp.rangeOfString("Auth0.setClient(") {
jsonp.removeRange(prefixRange)
}
if let suffixRange = jsonp.rangeOfString(");") {
jsonp.removeRange(suffixRange)
}

do {
var connections = OfflineConnections()
let json = try NSJSONSerialization.JSONObjectWithData(jsonp.dataUsingEncoding(NSUTF8StringEncoding)!, options: []) as? JSONObject
self.logger.debug("Client configuration is \(json)")
let info = ClientInfo(json: json)
if let auth0 = info.auth0, let connection = auth0.connections.first {
let requiresUsername = connection.booleanValue(forKey: "requires_username")
connections.database(name: connection.name, requiresUsername: requiresUsername)
}
info.oauth2.forEach { strategy in
strategy.connections.forEach { connections.social(name: $0.name, style: AuthStyle.style(forStrategy: strategy.name, connectionName: $0.name)) }
}
callback(connections)
} catch let e {
self.logger.error("Failed to parse \(jsonp) with error \(e)")
return callback(nil)
}
}
task.resume()
}
}

private struct ClientInfo {
let json: JSONObject?

var strategies: [StrategyInfo] {
let list = json?["strategies"] as? JSONArray ?? []
return list
.filter { $0["name"] != nil }
.map { StrategyInfo(json: $0) }
}

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) } }

let passwordlessStrategyNames = [
"email",
"sms"
]

let enterpriseStrategyNames = [
"google-apps",
"google-openid",
"office365",
"waad",
"adfs",
"ad",
"samlp",
"pingfederate",
"ip",
"mscrm",
"custom",
"sharepoint",
]

}

private struct StrategyInfo {
let json: JSONObject

var name: String { return json["name"] as! String }

var connections: [ConnectionInfo] {
let list = json["connections"] as? JSONArray ?? []
return list
.filter { $0["name"] != nil }
.map { ConnectionInfo(json: $0) }
}
}

private struct ConnectionInfo {

let json: JSONObject

var name: String { return json["name"] as! String }

func booleanValue(forKey key: String, defaultValue: Bool = false) -> Bool { return json[key] as? Bool ?? defaultValue }
}

private func cdnURL(from url: NSURL) -> NSURL {
guard let host = url.host where host.hasSuffix(".auth0.com") else { return url }
let components = host.componentsSeparatedByString(".")
guard components.count == 4 else { return NSURL(string: "https://cdn.auth0.com")! }
let region = components[1]
return NSURL(string: "https://cdn.\(region).auth0.com")!
}
45 changes: 45 additions & 0 deletions ConnectionLoadingPresenter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// ConnectionLoadingPresenter.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 ConnectionLoadingPresenter: Presentable, Loggable {
var messagePresenter: MessagePresenter?
let loader: RemoteConnectionLoader
let navigator: Navigable

init(loader: RemoteConnectionLoader, navigator: Navigable) {
self.loader = loader
self.navigator = navigator
}

var view: View {
self.loader.load { connections in
guard let connections = connections where !connections.isEmpty else { return self.navigator.exit(withError: UnrecoverableError.ClientWithNoConnections) }
Queue.main.async {
self.logger.debug("Loaded connections. Moving to root view")
self.navigator.reload(withConnections: connections)
}
}
return LoadingView()
}
}
66 changes: 66 additions & 0 deletions LoadingView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// LoadingView.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 UIKit

class LoadingView: UIView, View {

weak var indicator: UIActivityIndicatorView?

var inProgress: Bool {
get {
return self.indicator?.isAnimating() ?? false
}
set {
Queue.main.async {
if newValue {
self.indicator?.startAnimating()
} else {
self.indicator?.stopAnimating()
}
}
}
}

// MARK:- Initialisers

init() {
super.init(frame: CGRectZero)
self.backgroundColor = .whiteColor()

let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
activityIndicator.color = UIColor ( red: 0.5725, green: 0.5804, blue: 0.5843, alpha: 1.0 )
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(activityIndicator)
constraintEqual(anchor: activityIndicator.centerXAnchor, toAnchor: self.centerXAnchor)
constraintEqual(anchor: activityIndicator.centerYAnchor, toAnchor: self.centerYAnchor)

self.indicator = activityIndicator
self.indicator?.startAnimating()
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}


}
Loading

0 comments on commit 4a03274

Please sign in to comment.