Skip to content

Commit

Permalink
Merge pull request #28 from auth0/feature-mfa-resource-owner
Browse files Browse the repository at this point in the history
Multifactor for /oauth/ro login
  • Loading branch information
hzalaz authored Jun 20, 2016
2 parents fd48ff8 + ab39f19 commit b11de7c
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

# Auth0
Auth0.plist
!Auth0Tests/Auth0.plist

# Xcode
build/
Expand Down
6 changes: 6 additions & 0 deletions Auth0.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
5FE2F8C41CD1498E003628F4 /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8C21CD1498E003628F4 /* UserProfile.swift */; };
5FE2F8C61CD1522F003628F4 /* UserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8C51CD1522F003628F4 /* UserProfileSpec.swift */; };
5FE2F8C71CD1522F003628F4 /* UserProfileSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FE2F8C51CD1522F003628F4 /* UserProfileSpec.swift */; };
5FE686A11D1877C10075874C /* Auth0.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5FE686A01D1877C10075874C /* Auth0.plist */; };
5FE686A21D1877C10075874C /* Auth0.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5FE686A01D1877C10075874C /* Auth0.plist */; };
5FF465BC1CE2AC4500F7ED8C /* Management.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF465BB1CE2AC4500F7ED8C /* Management.swift */; };
5FF465BD1CE2AC4500F7ED8C /* Management.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FF465BB1CE2AC4500F7ED8C /* Management.swift */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -254,6 +256,7 @@
5FE2F8BD1CD0EC52003628F4 /* ResponseSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseSpec.swift; sourceTree = "<group>"; };
5FE2F8C21CD1498E003628F4 /* UserProfile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
5FE2F8C51CD1522F003628F4 /* UserProfileSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserProfileSpec.swift; sourceTree = "<group>"; };
5FE686A01D1877C10075874C /* Auth0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = "<group>"; };
5FF346491CEFEC04000799DE /* Auth0Tests.iOS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Auth0Tests.iOS-Bridging-Header.h"; sourceTree = "<group>"; };
5FF3464A1CEFEC04000799DE /* Auth0Tests.OSX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Auth0Tests.OSX-Bridging-Header.h"; sourceTree = "<group>"; };
5FF465BB1CE2AC4500F7ED8C /* Management.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Management.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -360,6 +363,7 @@
5FF3464A1CEFEC04000799DE /* Auth0Tests.OSX-Bridging-Header.h */,
5FF346491CEFEC04000799DE /* Auth0Tests.iOS-Bridging-Header.h */,
5F06DD951CC451430011842B /* Info.plist */,
5FE686A01D1877C10075874C /* Auth0.plist */,
);
path = Auth0Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -758,13 +762,15 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5FE686A11D1877C10075874C /* Auth0.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
5F06DDAD1CC451700011842B /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5FE686A21D1877C10075874C /* Auth0.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
18 changes: 10 additions & 8 deletions Auth0/Auth0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ public func authentication(clientId clientId: String, domain: String, session: N
```

- parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession
- parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle

- returns: Auth0 Authentication API
- important: Calling this method without a valid `Auth0.plist` will crash your application
*/
public func authentication(session session: NSURLSession = .sharedSession()) -> Authentication {
let values = plistValues()!
public func authentication(session session: NSURLSession = .sharedSession(), bundle: NSBundle = NSBundle.mainBundle()) -> Authentication {
let values = plistValues(bundle: bundle)!
return authentication(clientId: values.clientId, domain: values.domain, session: session)
}

Expand Down Expand Up @@ -95,13 +96,14 @@ public func authentication(session session: NSURLSession = .sharedSession()) ->

- parameter token: token of Management API v2 with the correct allowed scopes to perform the desired action
- parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession
- parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle

- returns: Auth0 Management API v2
- important: Auth0.swift has yet to implement all endpoints. Now you can only perform some CRUD operations against Users
- important: Calling this method without a valid `Auth0.plist` will crash your application
*/
public func management(token token: String, session: NSURLSession = .sharedSession()) -> Management {
let values = plistValues()!
public func management(token token: String, session: NSURLSession = .sharedSession(), bundle: NSBundle = NSBundle.mainBundle()) -> Management {
let values = plistValues(bundle: bundle)!
return management(token: token, domain: values.domain, session: session)
}

Expand Down Expand Up @@ -154,12 +156,13 @@ public func management(token token: String, domain: String, session: NSURLSessio

- parameter token: token of Management API v2 with the correct allowed scopes to perform the desired action
- parameter session: instance of NSURLSession used for networking. By default it will use the shared NSURLSession
- parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle

- returns: Auth0 Management API v2
- important: Calling this method without a valid `Auth0.plist` will crash your application
*/
public func users(token token: String, session: NSURLSession = .sharedSession()) -> Users {
let values = plistValues()!
public func users(token token: String, session: NSURLSession = .sharedSession(), bundle: NSBundle = NSBundle.mainBundle()) -> Users {
let values = plistValues(bundle: bundle)!
return users(token: token, domain: values.domain, session: session)
}

Expand Down Expand Up @@ -198,8 +201,7 @@ public func enableLogging(enabled enabled: Bool = true) {
Auth0Logger.sharedInstance.logger = enabled ? DefaultLogger() : nil
}

func plistValues() -> (clientId: String, domain: String)? {
let bundle = NSBundle.mainBundle()
func plistValues(bundle bundle: NSBundle) -> (clientId: String, domain: String)? {
guard
let path = bundle.pathForResource("Auth0", ofType: "plist"),
let values = NSDictionary(contentsOfFile: path) as? [String: AnyObject]
Expand Down
23 changes: 10 additions & 13 deletions Auth0/Authentication/Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ public struct Authentication {

- parameter usernameOrEmail: username or email used of the user to authenticate, e.g. in email in Database connections or phone number for SMS connections.
- parameter password: password of the user or one time password (OTP) for passwordless connection users
- parameter multifactorCode: multifactor code if the user has enrolled one. e.g. Guardian. By default is `nil` and no code is sent.
- parameter connection: name of any of your configured database or passwordless connections
- parameter scope: scope value requested when authenticating the user. Default is 'openid'
- parameter parameters: additional parameters that are optionally sent with the authentication request

- returns: authentication request that will yield Auth0 User Credentials
- seeAlso: Credentials
*/
public func login(usernameOrEmail username: String, password: String, connection: String, scope: String = "openid", parameters: [String: AnyObject] = [:]) -> Request<Credentials, AuthenticationError> {
public func login(usernameOrEmail username: String, password: String, multifactorCode: String? = nil, connection: String, scope: String = "openid", parameters: [String: AnyObject] = [:]) -> Request<Credentials, AuthenticationError> {
let resourceOwner = NSURL(string: "/oauth/ro", relativeToURL: self.url)!
var payload: [String: AnyObject] = [
"username": username,
Expand All @@ -98,6 +99,7 @@ public struct Authentication {
"scope": scope,
"client_id": self.clientId,
]
payload["mfa_code"] = multifactorCode
parameters.forEach { key, value in payload[key] = value }
return Request(session: session, url: resourceOwner, method: "POST", handle: authenticationObject, payload: payload)
}
Expand Down Expand Up @@ -131,11 +133,11 @@ public struct Authentication {
.start { print($0) }
```

- parameter email: email of the user to create
- parameter username: username of the user if the connection requires username. By default is 'nil'
- parameter password: password for the new user
- parameter connection: name where the user will be created (Database connection)
- parameter userMetadata: additional userMetadata parameters that will be added to the newly created user.
- parameter email: email of the user to create
- parameter username: username of the user if the connection requires username. By default is 'nil'
- parameter password: password for the new user
- parameter connection: name where the user will be created (Database connection)
- parameter userMetadata: additional userMetadata parameters that will be added to the newly created user.

- returns: request that will yield a created database user (just email, username and email verified flag)
*/
Expand All @@ -146,13 +148,8 @@ public struct Authentication {
"connection": connection,
"client_id": self.clientId,
]
if let username = username {
payload["username"] = username
}

if let userMetadata = userMetadata {
payload["user_metadata"] = userMetadata
}
payload["username"] = username
payload["user_metadata"] = userMetadata

let createUser = NSURL(string: "/dbconnections/signup", relativeToURL: self.url)!
return Request(session: session, url: createUser, method: "POST", handle: databaseUser, payload: payload)
Expand Down
6 changes: 4 additions & 2 deletions Auth0/WebAuth/WebAuth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ import SafariServices
</plist>
```

- parameter bundle: bundle used to locate the `Auth0.plist` file. By default is the main bundle

- returns: Auth0 WebAuth component
- important: Calling this method without a valid `Auth0.plist` will crash your application
*/
public func webAuth() -> WebAuth {
let values = plistValues()!
public func webAuth(bundle bundle: NSBundle = NSBundle.mainBundle()) -> WebAuth {
let values = plistValues(bundle: bundle)!
return webAuth(clientId: values.clientId, domain: values.domain)
}

Expand Down
10 changes: 10 additions & 0 deletions Auth0Tests/Auth0.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Domain</key>
<string>samples.auth0.com</string>
<key>ClientId</key>
<string>CLIENT_ID</string>
</dict>
</plist>
36 changes: 35 additions & 1 deletion Auth0Tests/Auth0Spec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,41 @@ class Auth0Spec: QuickSpec {
it("should return authentication endpoint with domain url") {
let domain = "https://mycustomdomain.com"
let auth = Auth0.authentication(clientId: ClientId, domain: domain)
expect(auth.url.absoluteString).to(equal(domain))
expect(auth.url.absoluteString) == domain
}

it("should return management endopoint") {
let management = Auth0.management(token: "token", domain: Domain)
expect(management.token) == "token"
expect(management.url.absoluteString) == "https://\(Domain)"
}

it("should return users endopoint") {
let users = Auth0.users(token: "token", domain: Domain)
expect(users.management.token) == "token"
expect(users.management.url.absoluteString) == "https://\(Domain)"
}

}

describe("plist loading") {

let bundle = NSBundle(forClass: Auth0Spec.classForCoder())

it("should return authentication endpoint with account from plist") {
let auth = Auth0.authentication(bundle: bundle)
expect(auth.url.absoluteString) == "https://samples.auth0.com"
expect(auth.clientId) == "CLIENT_ID"
}

it("should return management endpoint with domain from plist") {
let management = Auth0.management(token: "TOKEN", bundle: bundle)
expect(management.url.absoluteString) == "https://samples.auth0.com"
}

it("should return users endpoint with domain from plist") {
let users = Auth0.users(token: "TOKEN", bundle: bundle)
expect(users.management.url.absoluteString) == "https://samples.auth0.com"
}

}
Expand Down
34 changes: 34 additions & 0 deletions Auth0Tests/Authentication/AuthenticationSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -550,5 +550,39 @@ class AuthenticationSpec: QuickSpec {

}

describe("resource owner multifactor") {

var code: String!

beforeEach {
code = NSUUID().UUIDString.stringByReplacingOccurrencesOfString("-", withString: "")
stub(isResourceOwner(Domain) && hasAtLeast(["username":SupportAtAuth0, "password": ValidPassword]) && hasNoneOf(["mfa_code"])) { _ in return authFailure(error: "a0.mfa_required", description: "need multifactor") }.name = "MFA Required"
stub(isResourceOwner(Domain) && hasAtLeast(["username": SupportAtAuth0, "password": ValidPassword, "mfa_code": code])) { _ in return authResponse(accessToken: AccessToken) }.name = "MFA Login"
}

it("should report multifactor is required") {
waitUntil(timeout: Timeout) { done in
auth
.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, connection: ConnectionName)
.start { result in
expect(result).to(beFailure { (error: AuthenticationError) in return error.isMultifactorRequired })
done()
}
}
}

it("should login with multifactor") {
waitUntil(timeout: Timeout) { done in
auth
.login(usernameOrEmail: SupportAtAuth0, password: ValidPassword, multifactorCode: code, connection: ConnectionName)
.start { result in
expect(result).to(haveCredentials())
done()
}
}
}

}

}
}
10 changes: 9 additions & 1 deletion Auth0Tests/Utils/Matchers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ func hasAllOf(parameters: [String: String]) -> OHHTTPStubsTestBlock {
func hasAtLeast(parameters: [String: String]) -> OHHTTPStubsTestBlock {
return { request in
guard let payload = request.a0_payload else { return false }
return parameters.filter { (key, _) in payload.contains { (name, _) in key == name } }.reduce(true, combine: { (initial, entry) -> Bool in
let entries = parameters.filter { (key, _) in payload.contains { (name, _) in key == name } }
return entries.count == parameters.count && entries.reduce(true, combine: { (initial, entry) -> Bool in
return initial && payload[entry.0] as? String == entry.1
})
}
Expand All @@ -57,6 +58,13 @@ func hasObjectAttribute(name: String, value: [String: String]) -> OHHTTPStubsTes
}
}

func hasNoneOf(names: [String]) -> OHHTTPStubsTestBlock {
return { request in
guard let payload = request.a0_payload else { return false }
return payload.filter { names.contains($0.0) }.isEmpty
}
}

func hasNoneOf(parameters: [String: String]) -> OHHTTPStubsTestBlock {
return !hasAtLeast(parameters)
}
Expand Down

0 comments on commit b11de7c

Please sign in to comment.