From 9a9178c060938c584d49598b43feb2fc084ae038 Mon Sep 17 00:00:00 2001 From: ericjordanmossman Date: Thu, 1 Oct 2020 22:37:39 -0400 Subject: [PATCH] Define Swift `Credentials` as an enum (#6826) Define Swift `Credentials` as an enum --- CHANGELOG.md | 6 +- .../ObjectServerTests/RLMObjectServerTests.mm | 3 +- .../SwiftObjectServerTests.swift | 83 +++++++------------ .../ObjectServerTests/SwiftSyncTestCase.swift | 6 +- Realm/RLMApp.h | 2 +- Realm/RLMCredentials.h | 5 +- Realm/RLMCredentials.mm | 14 +--- Realm/RLMUser.h | 2 +- RealmSwift/App.swift | 42 +++++++++- RealmSwift/ObjectiveCSupport+Sync.swift | 24 ++++++ RealmSwift/Sync.swift | 20 ++++- 11 files changed, 126 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480c86c09a..a1c89fe12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,11 @@ x.y.z Release notes (yyyy-MM-dd) * ([#????](https://github.com/realm/realm-cocoa/issues/????), since v?.?.?) * None. - +### Breaking Changes +* Define `RealmSwift.Credentials` as an enum instead of a `typealias`. Example usage has changed from `Credentials(googleAuthCode: "token")` to `Credentials.google(serverAuthCode: "serverAuthCode")`, and `Credentials(facebookToken: "token")` to `Credentials.facebook(accessToken: "accessToken")`, etc. +* Remove error parameter and redefine payload in `+ (instancetype)credentialsWithFunctionPayload:(NSDictionary *)payload +error:(NSError **)error;`. It is now defined as `+ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload;` + ### Compatibility * File format: Generates Realms with format v12 (Reads and upgrades all previous formats) diff --git a/Realm/ObjectServerTests/RLMObjectServerTests.mm b/Realm/ObjectServerTests/RLMObjectServerTests.mm index c5a455af22..271c7946df 100644 --- a/Realm/ObjectServerTests/RLMObjectServerTests.mm +++ b/Realm/ObjectServerTests/RLMObjectServerTests.mm @@ -487,8 +487,7 @@ - (void)testAppleCredential { - (void)testFunctionCredential { NSError *error; - RLMCredentials *functionCredential = [RLMCredentials credentialsWithFunctionPayload:@{ @"dog" : @{ @"name" : @"fido" } } - error:&error]; + RLMCredentials *functionCredential = [RLMCredentials credentialsWithFunctionPayload:@{@"dog": @{@"name": @"fido"}}]; XCTAssertEqualObjects(functionCredential.provider, @"custom-function"); XCTAssertEqualObjects(error, nil); } diff --git a/Realm/ObjectServerTests/SwiftObjectServerTests.swift b/Realm/ObjectServerTests/SwiftObjectServerTests.swift index 193d6970bb..c775de0773 100644 --- a/Realm/ObjectServerTests/SwiftObjectServerTests.swift +++ b/Realm/ObjectServerTests/SwiftObjectServerTests.swift @@ -600,52 +600,25 @@ class SwiftObjectServerTests: SwiftSyncTestCase { } #endif - // MARK: - App Credentials + func testAppCredentialSupport() { + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.facebook(accessToken: "accessToken")), RLMCredentials(facebookToken: "accessToken")) - func testEmailPasswordCredential() { - let EmailPasswordCredential = Credentials(email: "email", password: "password") - XCTAssertEqual(EmailPasswordCredential.provider.rawValue, "local-userpass") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.google(serverAuthCode: "serverAuthCode")), RLMCredentials(googleAuthCode: "serverAuthCode")) - func testJWTCredentials() { - let jwtCredential = Credentials(jwt: "token") - XCTAssertEqual(jwtCredential.provider.rawValue, "custom-token") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.apple(idToken: "idToken")), RLMCredentials(appleToken: "idToken")) - func testAnonymousCredentials() { - let anonymousCredential = Credentials.anonymous() - XCTAssertEqual(anonymousCredential.provider.rawValue, "anon-user") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.emailPassword(email: "email", password: "password")), RLMCredentials(email: "email", password: "password")) - func testUserAPIKeyCredentials() { - let userAPIKeyCredential = Credentials(userAPIKey: "apikey") - XCTAssertEqual(userAPIKeyCredential.provider.rawValue, "api-key") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.jwt(token: "token")), RLMCredentials(jwt: "token")) - func testServerAPIKeyCredentials() { - let serverAPIKeyCredential = Credentials(serverAPIKey: "apikey") - XCTAssertEqual(serverAPIKeyCredential.provider.rawValue, "api-key") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.function(payload: ["dog": ["name": "fido"]])), + RLMCredentials(functionPayload: ["dog": ["name" as NSString: "fido" as NSString] as NSDictionary])) - func testFacebookCredentials() { - let facebookCredential = Credentials(facebookToken: "token") - XCTAssertEqual(facebookCredential.provider.rawValue, "oauth2-facebook") - } - - func testGoogleCredentials() { - let googleCredential = Credentials(googleAuthCode: "token") - XCTAssertEqual(googleCredential.provider.rawValue, "oauth2-google") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.userAPIKey("key")), RLMCredentials(userAPIKey: "key")) - func testAppleCredentials() { - let appleCredential = Credentials(appleToken: "token") - XCTAssertEqual(appleCredential.provider.rawValue, "oauth2-apple") - } + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.serverAPIKey("key")), RLMCredentials(serverAPIKey: "key")) - func testFunctionCredentials() { - var error: NSError! - let functionCredential = Credentials.init(functionPayload: ["dog": ["name": "fido"]], error: &error) - XCTAssertEqual(functionCredential.provider.rawValue, "custom-function") + XCTAssertEqual(ObjectiveCSupport.convert(object: Credentials.anonymous), RLMCredentials.anonymous()) } // MARK: - Authentication @@ -657,7 +630,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let user = try synchronouslyLogInUser(for: credentials) XCTAssertEqual(user.state, .loggedIn) - let credentials2 = Credentials(email: email, password: "NOT_A_VALID_PASSWORD") + let credentials2 = Credentials.emailPassword(email: email, password: "NOT_A_VALID_PASSWORD") let ex = expectation(description: "Should log in the user properly") self.app.login(credentials: credentials2, completion: { user2, error in @@ -742,7 +715,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let loginEx = expectation(description: "Login user") var syncUser: User? - app.login(credentials: Credentials(email: email, password: password)) { (user, error) in + app.login(credentials: Credentials.emailPassword(email: email, password: password)) { (user, error) in XCTAssertNil(error) syncUser = user loginEx.fulfill() @@ -782,7 +755,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { var syncUser1: User? var syncUser2: User? - app.login(credentials: Credentials(email: email1, password: password1)) { (user, error) in + app.login(credentials: Credentials.emailPassword(email: email1, password: password1)) { (user, error) in XCTAssertNil(error) syncUser1 = user login1Ex.fulfill() @@ -790,7 +763,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { wait(for: [login1Ex], timeout: 4.0) - app.login(credentials: Credentials(email: email2, password: password2)) { (user, error) in + app.login(credentials: Credentials.emailPassword(email: email2, password: password2)) { (user, error) in XCTAssertNil(error) syncUser2 = user login2Ex.fulfill() @@ -834,9 +807,9 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let loginEx = expectation(description: "Login user") var syncUser: User? - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) - app.login(credentials: Credentials.anonymous()) { (user, error) in + app.login(credentials: Credentials.anonymous) { (user, error) in XCTAssertNil(error) syncUser = user loginEx.fulfill() @@ -846,7 +819,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let linkEx = expectation(description: "Link user") - syncUser?.linkUser(with: credentials) { (user, error) in + syncUser?.linkUser(credentials: credentials) { (user, error) in XCTAssertNil(error) syncUser = user linkEx.fulfill() @@ -929,7 +902,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { wait(for: [registerUserEx], timeout: 4.0) let loginEx = expectation(description: "Login user") - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) var syncUser: User? app.login(credentials: credentials) { (user, error) in @@ -1004,7 +977,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let loginEx = expectation(description: "Login user") - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) var syncUser: User? app.login(credentials: credentials) { (user, error) in syncUser = user @@ -1046,7 +1019,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { let loginExpectation = expectation(description: "Login user") - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) app.login(credentials: credentials) { (_, error) in XCTAssertNil(error) loginExpectation.fulfill() @@ -1082,7 +1055,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { wait(for: [registerUserEx], timeout: 4.0) let loginEx = expectation(description: "Login user") - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) var syncUser: User? app.login(credentials: credentials) { (user, error) in syncUser = user @@ -1115,7 +1088,7 @@ class SwiftObjectServerTests: SwiftSyncTestCase { // MARK: - Mongo Client func testMongoClient() { - let user = try! synchronouslyLogInUser(for: Credentials.anonymous()) + let user = try! synchronouslyLogInUser(for: Credentials.anonymous) let mongoClient = user.mongoClient("mongodb1") XCTAssertEqual(mongoClient.name, "mongodb1") let database = mongoClient.database(named: "test_data") @@ -2138,7 +2111,7 @@ class CombineObjectServerTests: SwiftSyncTestCase { wait(for: [registerUserEx], timeout: 4.0) let loginEx = expectation(description: "Login user") - app.login(credentials: Credentials(email: email, password: password)) + app.login(credentials: Credentials.emailPassword(email: email, password: password)) .sink(receiveCompletion: { result in if case .failure = result { XCTFail("Should login") @@ -2169,7 +2142,7 @@ class CombineObjectServerTests: SwiftSyncTestCase { .store(in: &cancellable) wait(for: [registerUserEx], timeout: 4.0) - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) var syncUser: User! let loginEx = expectation(description: "Login user") app.login(credentials: credentials) @@ -2786,7 +2759,7 @@ class CombineObjectServerTests: SwiftSyncTestCase { .store(in: &cancellable) wait(for: [regEx], timeout: 4.0) - let credentials = Credentials(email: email, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) var syncUser: User! let loginEx = expectation(description: "Should login") app.login(credentials: credentials) @@ -2851,7 +2824,7 @@ class CombineObjectServerTests: SwiftSyncTestCase { let loginEx = expectation(description: "Login user") var syncUser: User? - app.login(credentials: Credentials(email: email, password: password)) + app.login(credentials: Credentials.emailPassword(email: email, password: password)) .sink(receiveCompletion: { _ in }, receiveValue: { (user) in syncUser = user @@ -2952,7 +2925,7 @@ class CombineObjectServerTests: SwiftSyncTestCase { wait(for: [registerUserEx], timeout: 4.0) let loginEx = expectation(description: "Login user") - app.login(credentials: Credentials(email: email, password: password)) + app.login(credentials: Credentials.emailPassword(email: email, password: password)) .sink(receiveCompletion: { _ in }, receiveValue: { _ in loginEx.fulfill() }) .store(in: &cancellable) diff --git a/Realm/ObjectServerTests/SwiftSyncTestCase.swift b/Realm/ObjectServerTests/SwiftSyncTestCase.swift index f197ed2f72..1b6bb2e52d 100644 --- a/Realm/ObjectServerTests/SwiftSyncTestCase.swift +++ b/Realm/ObjectServerTests/SwiftSyncTestCase.swift @@ -32,11 +32,11 @@ class SwiftSyncTestCase: RLMSyncTestCase { func basicCredentials(usernameSuffix: String = "", file: StaticString = #file, line: UInt = #line) -> Credentials { - let username = "\(randomString(10))\(usernameSuffix)" + let email = "\(randomString(10))\(usernameSuffix)" let password = "abcdef" - let credentials = Credentials(email: username, password: password) + let credentials = Credentials.emailPassword(email: email, password: password) let ex = expectation(description: "Should register in the user properly") - app.emailPasswordAuth.registerUser(email: username, password: password, completion: { error in + app.emailPasswordAuth.registerUser(email: email, password: password, completion: { error in XCTAssertNil(error) ex.fulfill() }) diff --git a/Realm/RLMApp.h b/Realm/RLMApp.h index 05621cf430..15676d5091 100644 --- a/Realm/RLMApp.h +++ b/Realm/RLMApp.h @@ -135,7 +135,7 @@ Create a new Realm App configuration. @param completion A callback invoked after completion. */ - (void)loginWithCredential:(RLMCredentials *)credentials - completion:(RLMUserCompletionBlock)completion NS_SWIFT_NAME(login(credentials:completion:)); + completion:(RLMUserCompletionBlock)completion NS_REFINED_FOR_SWIFT; /** Switches the active user to the specified user. diff --git a/Realm/RLMCredentials.h b/Realm/RLMCredentials.h index 57825c7cfe..f8c2b8d670 100644 --- a/Realm/RLMCredentials.h +++ b/Realm/RLMCredentials.h @@ -19,6 +19,7 @@ #import NS_ASSUME_NONNULL_BEGIN +@protocol RLMBSON; /// A token representing an identity provider's credentials. typedef NSString *RLMCredentialsToken; @@ -79,10 +80,8 @@ extern RLMIdentityProvider const RLMIdentityProviderServerAPIKey; /** Construct and return credentials for a MongoDB Realm function using a mongodb document as a json payload. - If the json can not be successfully serialised and error will be produced and the object will be nil. */ -+ (instancetype)credentialsWithFunctionPayload:(NSDictionary *)payload - error:(NSError **)error; ++ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload; /** Construct and return credentials from a user api key. diff --git a/Realm/RLMCredentials.mm b/Realm/RLMCredentials.mm index f781641d74..dd313cdacc 100644 --- a/Realm/RLMCredentials.mm +++ b/Realm/RLMCredentials.mm @@ -18,6 +18,7 @@ #import "RLMCredentials_Private.hpp" +#import "RLMBSON_Private.hpp" #import "RLMSyncUtil_Private.h" #import "RLMUtil.hpp" #import "util/bson/bson.hpp" @@ -56,17 +57,8 @@ + (instancetype)credentialsWithJWT:(NSString *)token { return [[self alloc] initWithAppCredentials:app::AppCredentials::custom(token.UTF8String)]; } -+ (instancetype)credentialsWithFunctionPayload:(NSDictionary *)payload - error:(NSError **)error { - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload - options:NSJSONWritingPrettyPrinted - error:error]; - if (!jsonData) { - return nil; - } - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - return [[self alloc] initWithAppCredentials:app::AppCredentials::function((realm::bson::BsonDocument)realm::bson::parse(jsonString.UTF8String))]; ++ (instancetype)credentialsWithFunctionPayload:(NSDictionary> *)payload { + return [[self alloc] initWithAppCredentials:app::AppCredentials::function(static_cast(RLMConvertRLMBSONToBson(payload)))]; } + (instancetype)credentialsWithUserAPIKey:(NSString *)apiKey { diff --git a/Realm/RLMUser.h b/Realm/RLMUser.h index 26a236771e..436d127a1e 100644 --- a/Realm/RLMUser.h +++ b/Realm/RLMUser.h @@ -142,7 +142,7 @@ NS_ASSUME_NONNULL_BEGIN `RLMUser` object representing the currently logged in user. */ - (void)linkUserWithCredentials:(RLMCredentials *)credentials - completion:(RLMOptionalUserBlock)completion; + completion:(RLMOptionalUserBlock)completion NS_REFINED_FOR_SWIFT; /** Removes the user diff --git a/RealmSwift/App.swift b/RealmSwift/App.swift index f27121b0f2..643d956d4e 100644 --- a/RealmSwift/App.swift +++ b/RealmSwift/App.swift @@ -74,14 +74,52 @@ public typealias PushClient = RLMPushClient /// An object which is used within UserAPIKeyProviderClient public typealias UserAPIKey = RLMUserAPIKey -/// A `Credentials` represents data that uniquely identifies a Realm Object Server user. -public typealias Credentials = RLMCredentials +/** +`Credentials`is an enum representing supported authentication types for MongoDB Realm. +Example Usage: +``` +let credentials = Credentials.JWT(token: myToken) +``` +*/ +public enum Credentials { + /// Credentials from a Facebook access token. + case facebook(accessToken: String) + /// Credentials from a Google serverAuthCode. + case google(serverAuthCode: String) + /// Credentials from an Apple id token. + case apple(idToken: String) + /// Credentials from an email and password. + case emailPassword(email: String, password: String) + /// Credentials from a JSON Web Token + case jwt(token: String) + /// Credentials for a MongoDB Realm function using a mongodb document as a json payload. + /// If the json can not be successfully serialised and error will be produced and the object will be nil. + case function(payload: Document) + /// Credentials from a user api key. + case userAPIKey(String) + /// Credentials from a sever api key. + case serverAPIKey(String) + /// Represents anonymous credentials + case anonymous +} /// The `App` has the fundamental set of methods for communicating with a Realm /// application backend. /// This interface provides access to login and authentication. public typealias App = RLMApp +extension App { + /** + Login to a user for the Realm app. + + @param credentials The credentials identifying the user. + @param completion A callback invoked after completion. + */ + public func login(credentials: Credentials, completion: @escaping RLMUserCompletionBlock) { + self.__login(withCredential: ObjectiveCSupport.convert(object: credentials), completion: completion) + } +} + /// Use this delegate to be provided a callback once authentication has succeed or failed @available(OSX 10.15, watchOS 6.0, iOS 13.0, iOSApplicationExtension 13.0, OSXApplicationExtension 10.15, tvOS 13.0, *) public typealias ASLoginDelegate = RLMASLoginDelegate diff --git a/RealmSwift/ObjectiveCSupport+Sync.swift b/RealmSwift/ObjectiveCSupport+Sync.swift index a750a0072e..09b3a3dc74 100644 --- a/RealmSwift/ObjectiveCSupport+Sync.swift +++ b/RealmSwift/ObjectiveCSupport+Sync.swift @@ -31,4 +31,28 @@ public extension ObjectiveCSupport { static func convert(object: RLMSyncConfiguration) -> SyncConfiguration { return SyncConfiguration(config: object) } + + /// Convert a `Credentials` to a `RLMCredentials` + static func convert(object: Credentials) -> RLMCredentials { + switch object { + case .facebook(let accessToken): + return RLMCredentials(facebookToken: accessToken) + case .google(let serverAuthCode): + return RLMCredentials(googleAuthCode: serverAuthCode) + case .apple(let idToken): + return RLMCredentials(appleToken: idToken) + case .emailPassword(let email, let password): + return RLMCredentials(email: email, password: password) + case .jwt(let token): + return RLMCredentials(jwt: token) + case .function(let payload): + return RLMCredentials(functionPayload: ObjectiveCSupport.convert(object: AnyBSON(payload))! as! [String: RLMBSON]) + case .userAPIKey(let APIKey): + return RLMCredentials(userAPIKey: APIKey) + case .serverAPIKey(let serverAPIKey): + return RLMCredentials(serverAPIKey: serverAPIKey) + case .anonymous: + return RLMCredentials.anonymous() + } + } } diff --git a/RealmSwift/Sync.swift b/RealmSwift/Sync.swift index 30499e7952..5e22f43aed 100644 --- a/RealmSwift/Sync.swift +++ b/RealmSwift/Sync.swift @@ -26,6 +26,22 @@ import Realm.Private */ public typealias User = RLMUser +extension User { + /** + Links the currently authenticated user with a new identity, where the identity is defined by the credential + specified as a parameter. This will only be successful if this `User` is the currently authenticated + with the client from which it was created. On success a new user will be returned with the new linked credentials. + + @param credentials The credentials used to link the user to a new identity. + @param completion The completion handler to call when the linking is complete. + If the operation is successful, the result will contain a new + `User` object representing the currently logged in user. + */ + public func linkUser(credentials: Credentials, completion: @escaping RLMOptionalUserBlock) { + self.__linkUser(with: ObjectiveCSupport.convert(object: credentials), completion: completion) + } +} + /** A singleton which configures and manages MongoDB Realm synchronization-related functionality. @@ -575,9 +591,9 @@ public extension User { /// with the client from which it was created. On success a new user will be returned with the new linked credentials. /// @param credentials The `Credentials` used to link the user to a new identity. /// @returns A publisher that eventually return `Result.success` or `Error`. - func linkUser(with credentials: Credentials) -> Future { + func linkUser(credentials: Credentials) -> Future { return Future { promise in - self.linkUser(with: credentials) { user, error in + self.linkUser(credentials: credentials) { user, error in if let user = user { promise(.success(user)) } else {