diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index 032b936c201..6f19ef7b093 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -17,6 +17,7 @@ #import "MainViewController.h" #import +#import #import #import @@ -25,6 +26,7 @@ #import "AppManager.h" #import "AuthCredentials.h" #import "FIRAdditionalUserInfo.h" +#import "FIRGameCenterAuthProvider.h" #import "FIROAuthProvider.h" #import "FIRPhoneAuthCredential.h" #import "FIRPhoneAuthProvider.h" @@ -589,11 +591,41 @@ static NSString *const kPhoneNumberSignInReCaptchaTitle = @"Sign in With Phone Number"; /** @var kVerifyIOSClientTitle - @brief The title for button to verify iOS client. + @brief The title for button to verify iOS client. */ static NSString *const kVerifyIOSClientTitle = @"Verify iOS client"; -/** @var kIsNewUserToggleTitle +/** @var kGameCenterAuthSectionTitle + @brief The title for the section of Game Center + */ +static NSString *const kGameCenterAuthSectionTitle = @"Game Center"; + +/** @var kLogInWithSystemGameCenterTitle + @brief The title for the button to log into the Game Center account + */ +static NSString *const kLogInWithSystemGameCenterTitle = @"Log In System Game Center"; + +/** @var kSignInWithGameCenterTitle + @brief The title for the button to sign in with Game Center + */ +static NSString *const kSignInWithGameCenterTitle = @"Sign in Game Center"; + +/** @var kLinkWithGameCenterTitle + @brief The title for the button to link with Game Center + */ +static NSString *const kLinkWithGameCenterTitle = @"Link Game Center"; + +/** @var kUnlinkWithGameCenterTitle + @brief The title for the button to unlink with Game Center + */ +static NSString *const kUnlinkWithGameCenterTitle = @"Unlink Game Center"; + +/** @var kReauthenticateWithGameCenterTitle + @brief The title for the button to reauthenticate with Game Center + */ +static NSString *const kReauthenticateWithGameCenterTitle = @"Reauthenticate Game Center"; + +/** @var kNewOrExistingUserToggleTitle @brief The title for button to enable new or existing user toggle. */ static NSString *const kNewOrExistingUserToggleTitle = @"New or Existing User Toggle"; @@ -707,6 +739,8 @@ - (id)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundl } - (void)viewDidLoad { + [super viewDidLoad]; + // Give us a circle for the image view: _userInfoTableViewCell.userInfoProfileURLImageView.layer.cornerRadius = _userInfoTableViewCell.userInfoProfileURLImageView.frame.size.width / 2.0f; @@ -745,6 +779,20 @@ - (void)updateTable { _isNewUserToggleOn = !_isNewUserToggleOn; [self updateTable]; }], ]], + [StaticContentTableViewSection sectionWithTitle:kGameCenterAuthSectionTitle cells:@[ + [StaticContentTableViewCell cellWithTitle:kLogInWithSystemGameCenterTitle + action:^{ [weakSelf logInWithSystemGameCenter]; }], + [StaticContentTableViewCell cellWithTitle:kSignInWithGameCenterTitle + action:^{ [weakSelf signInWithGameCenter]; }], + [StaticContentTableViewCell cellWithTitle:kLinkWithGameCenterTitle + action:^{ [weakSelf linkWithGameCenter]; }], + [StaticContentTableViewCell cellWithTitle:kUnlinkWithGameCenterTitle + action:^{ + [weakSelf unlinkFromProvider:FIRGameCenterAuthProviderID completion:nil]; + }], + [StaticContentTableViewCell cellWithTitle:kReauthenticateWithGameCenterTitle + action:^{ [weakSelf reauthenticateWithGameCenter]; }], + ]], [StaticContentTableViewSection sectionWithTitle:kPhoneAuthSectionTitle cells:@[ [StaticContentTableViewCell cellWithTitle:kPhoneNumberSignInReCaptchaTitle action:^{ [weakSelf signInWithPhoneNumberWithPrompt]; }], @@ -1726,6 +1774,83 @@ - (void)signInFacebookAndRetrieveData { [self signinWithProvider:[AuthProviders facebook] retrieveData:YES]; } +- (void)logInWithSystemGameCenter { + GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; + localPlayer.authenticateHandler = ^(UIViewController * _Nullable viewController, + NSError * _Nullable error) { + if (error) { + [self showTypicalUIForUserUpdateResultsWithTitle:@"Game Center Error" error:error]; + } else if (viewController != nil) { + [self presentViewController:viewController animated:YES completion:nil]; + } + }; +} + +- (void)signInWithGameCenter { + [FIRGameCenterAuthProvider getCredentialWithCompletion: + ^(FIRAuthCredential * _Nullable credential, NSError * _Nullable error) { + if (error) { + [self showTypicalUIForUserUpdateResultsWithTitle:@"Game Center Error" error:error]; + } else { + [[AppManager auth] signInAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable result, + NSError * _Nullable error) { + [self hideSpinner:^{ + if (error) { + [self logFailure:@"Sign in with Game Center failed" error:error]; + } else { + [self logSuccess:@"Sign in with Game Center succeeded."]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Sign In Error" error:error]; + }]; + }]; + } + }]; +} + +- (void)linkWithGameCenter { + [FIRGameCenterAuthProvider getCredentialWithCompletion: + ^(FIRAuthCredential * _Nullable credential, NSError * _Nullable error) { + if (error) { + [self showTypicalUIForUserUpdateResultsWithTitle:@"Game Center Error" error:error]; + } else { + [[self user] linkAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable result, + NSError * _Nullable error) { + [self hideSpinner:^{ + if (error) { + [self logFailure:@"Link with Game Center failed" error:error]; + } else { + [self logSuccess:@"Link with Game Center succeeded."]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Link Error" error:error]; + }]; + }]; + } + }]; +} + +- (void)reauthenticateWithGameCenter { + [FIRGameCenterAuthProvider getCredentialWithCompletion: + ^(FIRAuthCredential * _Nullable credential, NSError * _Nullable error) { + if (error) { + [self showTypicalUIForUserUpdateResultsWithTitle:@"Game Center Error" error:error]; + } else { + [[self user] reauthenticateAndRetrieveDataWithCredential:credential + completion:^(FIRAuthDataResult * _Nullable result, + NSError * _Nullable error) { + [self hideSpinner:^{ + if (error) { + [self logFailure:@"Reauthenticate with Game Center failed" error:error]; + } else { + [self logSuccess:@"Reauthenticate with Game Center succeeded."]; + } + [self showTypicalUIForUserUpdateResultsWithTitle:@"Reauthenticate Error" error:error]; + }]; + }]; + } + }]; +} /** @fn signInEmailPassword @brief Invoked when "Sign in with Email/Password" row is pressed. */ diff --git a/Example/Auth/Tests/FIRSignInWithGameCenterTests.m b/Example/Auth/Tests/FIRSignInWithGameCenterTests.m new file mode 100644 index 00000000000..ccb83432d85 --- /dev/null +++ b/Example/Auth/Tests/FIRSignInWithGameCenterTests.m @@ -0,0 +1,247 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthBackend.h" +#import "FIRFakeBackendRPCIssuer.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" + +/** @var kTestAPIKey + @brief Fake API key used for testing. + */ +static NSString *const kTestAPIKey = @"APIKEY"; + +/** @var kExpectedAPIURL + @brief The expected URL for the test calls. + */ +static NSString *const kExpectedAPIURL = + @"https://www.googleapis.com/identitytoolkit/v3/relyingparty/signInWithGameCenter?key=APIKEY"; + +/** @var kIDTokenKey + @brief The key of the id token. + */ +static NSString *const kIDTokenKey = @"idToken"; + +/** @var kIDToken + @brief The testing id token. + */ +static NSString *const kIDToken = @"IDTOKEN"; + +/** @var kRefreshTokenKey + @brief The key of the refresh token. + */ +static NSString *const kRefreshTokenKey = @"refreshToken"; + +/** @var kRefreshToken + @brief The testing refresh token. + */ +static NSString *const kRefreshToken = @"PUBLICKEYURL"; + +/** @var kLocalIDKey + @brief The key of local id. + */ +static NSString *const kLocalIDKey = @"localId"; + +/** @var kLocalID + @brief The testing local id. + */ +static NSString *const kLocalID = @"LOCALID"; + +/** @var kPlayerIDKey + @brief The key of player id. + */ +static NSString *const kPlayerIDKey = @"playerId"; + +/** @var kPlayerID + @brief The testing player id. + */ +static NSString *const kPlayerID = @"PLAYERID"; + +/** @var kApproximateExpirationDateKey + @brief The approximate expiration date key. + */ +static NSString *const kApproximateExpirationDateKey = @"expiresIn"; + +/** @var kApproximateExpirationDate + @brief The testing approximate expration date. + */ +static NSString *const kApproximateExpirationDate = @"3600"; + +/** @var kIsNewUserKey + @brief The key of whether the user is new user. + */ +static NSString *const kIsNewUserKey = @"isNewUser"; + +/** @var kIsNewUser + @brief The testing isNewUser. + */ +static BOOL const kIsNewUser = YES; + +/** @var kDisplayNameKey + @brief The key of display name. + */ +static NSString *const kDisplayNameKey = @"displayName"; + +/** @var kDisplayName + @brief The testing display name. + */ +static NSString *const kDisplayName = @"DISPLAYNAME"; + +/** @var kPublicKeyURLKey + @brief The key of public key url. + */ +static NSString *const kPublicKeyURLKey = @"publicKeyUrl"; + +/** @var kPublicKeyURL + @brief The testing public key url. + */ +static NSString *const kPublicKeyURL = @"PUBLICKEYURL"; + +/** @var kSignatureKey + @brief The key of the signature. + */ +static NSString *const kSignatureKey = @"signature"; + +/** @var kSignature + @brief The testing signature. + */ +static NSString *const kSignature = @"AAAABBBBCCCC"; + +/** @var kSaltKey + @brief The key of the salt. + */ +static NSString *const kSaltKey = @"salt"; + +/** @var kSalt + @brief The testing salt. + */ +static NSString *const kSalt = @"AAAA"; + +/** @var kTimestampKey + @brief The key of the timestamp. + */ +static NSString *const kTimestampKey = @"timestamp"; + +/** @var kTimestamp + @brief The testing timestamp. + */ +static uint64_t const kTimestamp = 12345678; + +/** @var kAccessTokenKey + @brief The key of the access token. + */ +static NSString *const kAccessTokenKey = @"idToken"; + +/** @var kAccessToken + @brief The testing access token. + */ +static NSString *const kAccessToken = @"ACCESSTOKEN"; + +@interface FIRSignInWithGameCenterTests : XCTestCase + +/** @property RPCIssuer + @brief This backend RPC issuer is used to fake network responses for each test in the suite. + In the @c setUp method we initialize this and set @c FIRAuthBackend's RPC issuer to it. + */ +@property (nonatomic, strong) FIRFakeBackendRPCIssuer *RPCIssuer; + +/** @property requestConfiguration + @brief This is the request configuration used for testing. + */ +@property (nonatomic, strong) FIRAuthRequestConfiguration *requestConfiguration; + +@end + +@implementation FIRSignInWithGameCenterTests + +- (void)setUp { + [super setUp]; + + FIRFakeBackendRPCIssuer *RPCIssuer = [[FIRFakeBackendRPCIssuer alloc] init]; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:RPCIssuer]; + self.RPCIssuer = RPCIssuer; + self.requestConfiguration = [[FIRAuthRequestConfiguration alloc] initWithAPIKey:kTestAPIKey]; +} + +- (void)tearDown { + self.requestConfiguration = nil; + self.RPCIssuer = nil; + [FIRAuthBackend setDefaultBackendImplementationWithRPCIssuer:nil]; + + [super tearDown]; +} + +- (void)testRequestResponseEncoding { + NSData *signature = [[NSData alloc] initWithBase64EncodedString:kSignature options:0]; + NSData *salt = [[NSData alloc] initWithBase64EncodedString:kSalt options:0]; + FIRSignInWithGameCenterRequest *request = + [[FIRSignInWithGameCenterRequest alloc] initWithPlayerID:kPlayerID + publicKeyURL:[NSURL URLWithString:kPublicKeyURL] + signature:signature + salt:salt + timestamp:kTimestamp + displayName:kDisplayName + requestConfiguration:self.requestConfiguration]; + request.accessToken = kAccessToken; + + __block BOOL callbackInvoked; + __block FIRSignInWithGameCenterResponse *RPCResponse; + __block NSError *RPCError; + + [FIRAuthBackend signInWithGameCenter:request + callback:^(FIRSignInWithGameCenterResponse *_Nullable response, + NSError *_Nullable error) { + RPCResponse = response; + RPCError = error; + callbackInvoked = YES; + }]; + + XCTAssertEqualObjects(self.RPCIssuer.requestURL.absoluteString, kExpectedAPIURL); + XCTAssertNotNil(self.RPCIssuer.decodedRequest); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kPlayerIDKey], kPlayerID); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kPublicKeyURLKey], kPublicKeyURL); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kSignatureKey], kSignature); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kSaltKey], kSalt); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kTimestampKey], + [NSNumber numberWithInteger:kTimestamp]); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kAccessTokenKey], kAccessToken); + XCTAssertEqualObjects(self.RPCIssuer.decodedRequest[kDisplayNameKey], kDisplayName); + + NSDictionary *jsonDictionary = @{ + @"idToken" : kIDToken, + @"refreshToken" : kRefreshToken, + @"localId" : kLocalID, + @"playerId" : kPlayerID, + @"expiresIn" : kApproximateExpirationDate, + @"isNewUser" : [NSNumber numberWithBool:kIsNewUser], + @"displayName" : kDisplayName, + }; + [self.RPCIssuer respondWithJSON:jsonDictionary]; + + XCTAssertTrue(callbackInvoked); + XCTAssertNotNil(RPCResponse); + XCTAssertEqualObjects(RPCResponse.IDToken, kIDToken); + XCTAssertEqualObjects(RPCResponse.refreshToken, kRefreshToken); + XCTAssertEqualObjects(RPCResponse.localID, kLocalID); + XCTAssertEqualObjects(RPCResponse.playerID, kPlayerID); + XCTAssertEqual(RPCResponse.isNewUser, kIsNewUser); + XCTAssertEqualObjects(RPCResponse.displayName, kDisplayName); +} + +@end diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index ea8fffa65dc..7debc404cc0 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -116,6 +116,7 @@ 0672F2F31EBBA7D900818E87 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0672F2F11EBBA7D900818E87 /* GoogleService-Info.plist */; }; 069428831EC3B38C00F7BC69 /* 1mb.dat in Resources */ = {isa = PBXBuildFile; fileRef = 069428801EC3B35A00F7BC69 /* 1mb.dat */; }; 06C24A061EC39BCB005208CA /* FIRStorageIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 06121ECA1EC39A0B0008D70E /* FIRStorageIntegrationTests.m */; }; + 408870AB21AE0218008AAE73 /* FIRSignInWithGameCenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 408870AA21AE0218008AAE73 /* FIRSignInWithGameCenterTests.m */; }; 409E1130219FA260000E6CFC /* FIRVerifyIOSClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 409E112F219FA260000E6CFC /* FIRVerifyIOSClientTests.m */; }; 7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */; }; 7EE21F7A1FE89193009B1370 /* FIREmailLinkRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */; }; @@ -954,6 +955,7 @@ 069428801EC3B35A00F7BC69 /* 1mb.dat */ = {isa = PBXFileReference; lastKnownFileType = file; path = 1mb.dat; sourceTree = ""; }; 0697B1201EC13D8A00542174 /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Base64.h; sourceTree = ""; }; 0697B1211EC13D8A00542174 /* Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Base64.m; sourceTree = ""; }; + 408870AA21AE0218008AAE73 /* FIRSignInWithGameCenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRSignInWithGameCenterTests.m; sourceTree = ""; }; 409E112F219FA260000E6CFC /* FIRVerifyIOSClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRVerifyIOSClientTests.m; sourceTree = ""; }; 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; @@ -2237,10 +2239,8 @@ DE9314F91E86C6FF0083EDBF /* Tests */ = { isa = PBXGroup; children = ( - DE9314FB1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.h */, - DE9315091E86C6FF0083EDBF /* FIRFakeBackendRPCIssuer.h */, - DE9315231E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.h */, DE9314FA1E86C6FF0083EDBF /* FIRAdditionalUserInfoTests.m */, + DE9314FB1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.h */, DE9314FC1E86C6FF0083EDBF /* FIRApp+FIRAuthUnitTests.m */, DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */, DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */, @@ -2253,16 +2253,17 @@ DE9315001E86C6FF0083EDBF /* FIRAuthGlobalWorkQueueTests.m */, DE9315011E86C6FF0083EDBF /* FIRAuthKeychainTests.m */, DE750DB81EB3DD4000A75E47 /* FIRAuthNotificationManagerTests.m */, - 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */, DE9315021E86C6FF0083EDBF /* FIRAuthSerialTaskQueueTests.m */, DE9315031E86C6FF0083EDBF /* FIRAuthTests.m */, 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */, - 7EE21F7B1FE8919D009B1370 /* FIREmailLinkSignInResponseTests.m */, DE9315041E86C6FF0083EDBF /* FIRAuthUserDefaultsStorageTests.m */, DE9315051E86C6FF0083EDBF /* FIRCreateAuthURIRequestTests.m */, DE9315061E86C6FF0083EDBF /* FIRCreateAuthURIResponseTests.m */, DE9315071E86C6FF0083EDBF /* FIRDeleteAccountRequestTests.m */, DE9315081E86C6FF0083EDBF /* FIRDeleteAccountResponseTests.m */, + 7EE21F791FE89193009B1370 /* FIREmailLinkRequestTests.m */, + 7EE21F7B1FE8919D009B1370 /* FIREmailLinkSignInResponseTests.m */, + DE9315091E86C6FF0083EDBF /* FIRFakeBackendRPCIssuer.h */, DE93150A1E86C6FF0083EDBF /* FIRFakeBackendRPCIssuer.m */, DE93150B1E86C6FF0083EDBF /* FIRGetAccountInfoRequestTests.m */, DE93150C1E86C6FF0083EDBF /* FIRGetAccountInfoResponseTests.m */, @@ -2278,6 +2279,7 @@ DE9315141E86C6FF0083EDBF /* FIRSendVerificationCodeResponseTests.m */, DE9315151E86C6FF0083EDBF /* FIRSetAccountInfoRequestTests.m */, DE9315161E86C6FF0083EDBF /* FIRSetAccountInfoResponseTests.m */, + 408870AA21AE0218008AAE73 /* FIRSignInWithGameCenterTests.m */, DE9315171E86C6FF0083EDBF /* FIRSignUpNewUserRequestTests.m */, DE9315181E86C6FF0083EDBF /* FIRSignUpNewUserResponseTests.m */, DE9315191E86C6FF0083EDBF /* FIRTwitterAuthProviderTests.m */, @@ -2293,6 +2295,7 @@ DE9315201E86C6FF0083EDBF /* FIRVerifyPasswordResponseTests.m */, DE9315211E86C6FF0083EDBF /* FIRVerifyPhoneNumberRequestTests.m */, DE9315221E86C6FF0083EDBF /* FIRVerifyPhoneNumberResponseTests.m */, + DE9315231E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.h */, DE9315241E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m */, DE9315251E86C6FF0083EDBF /* Tests-Info.plist */, ); @@ -4379,6 +4382,7 @@ DE9315621E86C71C0083EDBF /* FIRCreateAuthURIResponseTests.m in Sources */, DE93155A1E86C71C0083EDBF /* FIRAuthBackendRPCImplementationTests.m in Sources */, DE93157D1E86C71C0083EDBF /* FIRVerifyPhoneNumberResponseTests.m in Sources */, + 408870AB21AE0218008AAE73 /* FIRSignInWithGameCenterTests.m in Sources */, DE93157E1E86C71C0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */, 7EE21F7C1FE8919E009B1370 /* FIREmailLinkSignInResponseTests.m in Sources */, DE9315771E86C71C0083EDBF /* FIRVerifyAssertionResponseTests.m in Sources */, @@ -5750,7 +5754,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 4ANB9W7R3P; GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$SRCROOT/DynamicLinks/FDLBuilderTestAppObjC/Info.plist"; + INFOPLIST_FILE = $SRCROOT/DynamicLinks/FDLBuilderTestAppObjC/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; @@ -5784,7 +5788,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 4ANB9W7R3P; GCC_C_LANGUAGE_STANDARD = gnu11; - INFOPLIST_FILE = "$SRCROOT/DynamicLinks/FDLBuilderTestAppObjC/Info.plist"; + INFOPLIST_FILE = $SRCROOT/DynamicLinks/FDLBuilderTestAppObjC/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.h b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.h new file mode 100644 index 00000000000..a99b96e5251 --- /dev/null +++ b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.h @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthCredential.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRGameCenterAuthCredential + @brief Internal implementation of FIRAuthCredential for Game Center credentials. + */ +@interface FIRGameCenterAuthCredential : FIRAuthCredential + +/** @property playerID + @brief The ID of the Game Center local player. + */ +@property(nonatomic, readonly) NSString *playerID; + +/** @property publicKeyURL + @brief The URL for the public encryption key. + */ +@property(nonatomic, readonly) NSURL *publicKeyURL; + +/** @property signature + @brief The verification signature data generated. + */ +@property(nonatomic, readonly) NSData *signature; + +/** @property salt + @brief A random string used to compute the hash and keep it randomized. + */ +@property(nonatomic, readonly) NSData *salt; + +/** @property timestamp + @brief The date and time that the signature was created. + */ +@property(nonatomic, readonly) uint64_t timestamp; + +/** @property displayName + @brief The date and time that the signature was created. + */ +@property(nonatomic, readonly) NSString *displayName; + +/** @fn initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName: + @brief Designated initializer. + @param publicKeyURL The URL for the public encryption key. + @param signature The verification signature generated. + @param salt A random string used to compute the hash and keep it randomized. + @param timestamp The date and time that the signature was created. + */ +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName NS_DESIGNATED_INITIALIZER; + +/** @fn initWithProvider: + @brief Initializer with a provider name. + @param provider The provider name. + */ +- (nullable instancetype)initWithProvider:(NSString *)provider NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.m b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.m new file mode 100644 index 00000000000..4ccd408b3da --- /dev/null +++ b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthCredential.m @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGameCenterAuthCredential.h" + +#import "FIRAuthExceptionUtils.h" +#import "FIRAuthCredential_Internal.h" +#import "FIRGameCenterAuthProvider.h" +#import "FIRVerifyAssertionRequest.h" + +@implementation FIRGameCenterAuthCredential + +- (nullable instancetype)initWithProvider:(NSString *)provider { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Please call the designated initializer."]; + return nil; +} + +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName { + self = [super initWithProvider:FIRGameCenterAuthProviderID]; + if (self) { + _playerID = [playerID copy]; + _publicKeyURL = [publicKeyURL copy]; + _signature = [signature copy]; + _salt = [salt copy]; + _timestamp = timestamp; + _displayName = [displayName copy]; + } + return self; +} + +- (void)prepareVerifyAssertionRequest:(FIRVerifyAssertionRequest *)request { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"Attempt to call prepareVerifyAssertionRequest: on a FIRGameCenterAuthCredential."]; +} + +@end diff --git a/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthProvider.m b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthProvider.m new file mode 100644 index 00000000000..65f79a85965 --- /dev/null +++ b/Firebase/Auth/Source/AuthProviders/GameCenter/FIRGameCenterAuthProvider.m @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRGameCenterAuthProvider.h" + +#import + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthExceptionUtils.h" +#import "FIRGameCenterAuthCredential.h" + +@implementation FIRGameCenterAuthProvider + +- (instancetype)init { + [FIRAuthExceptionUtils raiseMethodNotImplementedExceptionWithReason: + @"This class is not meant to be initialized."]; + return nil; +} + ++ (void)getCredentialWithCompletion:(FIRGameCenterCredentialCallback)completion { + __weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; + if (!localPlayer.isAuthenticated) { + if (completion) { + completion(nil, [FIRAuthErrorUtils localPlayerNotAuthenticatedError]); + } + return; + } + + [localPlayer generateIdentityVerificationSignatureWithCompletionHandler: + ^(NSURL *publicKeyURL, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { + if (error) { + if (completion) { + completion(nil, error); + } + } else { + if (completion) { + /** + @c `localPlayer.alias` is actually the displayname needed, instead of + `localPlayer.displayname`. For more information, check + https://developer.apple.com/documentation/gamekit/gkplayer + **/ + NSString *displayName = localPlayer.alias; + FIRGameCenterAuthCredential *credential = + [[FIRGameCenterAuthCredential alloc] initWithPlayerID:localPlayer.playerID + publicKeyURL:publicKeyURL + signature:signature + salt:salt + timestamp:timestamp + displayName:displayName]; + completion(credential, nil); + } + } + }]; +} + +@end diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index 8235baf30ad..d634b489138 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -52,6 +52,7 @@ #import "FIRCreateAuthURIResponse.h" #import "FIREmailLinkSignInRequest.h" #import "FIREmailLinkSignInResponse.h" +#import "FIRGameCenterAuthCredential.h" #import "FIRGetOOBConfirmationCodeRequest.h" #import "FIRGetOOBConfirmationCodeResponse.h" #import "FIRResetPasswordRequest.h" @@ -60,6 +61,8 @@ #import "FIRSendVerificationCodeResponse.h" #import "FIRSetAccountInfoRequest.h" #import "FIRSetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" #import "FIRSignUpNewUserRequest.h" #import "FIRSignUpNewUserResponse.h" #import "FIRVerifyAssertionRequest.h" @@ -597,6 +600,40 @@ - (void)internalSignInAndRetrieveDataWithEmail:(NSString *)email callback:completion]; } +/** @fn signInWithGameCenterCredential:callback: + @brief Signs in using a game center credential. + @param credential The Game Center Auth Credential used to sign in. + @param callback A block which is invoked when the sign in finished (or is cancelled). Invoked + asynchronously on the global auth work queue in the future. + */ +- (void)signInWithGameCenterCredential:(FIRGameCenterAuthCredential *)credential + callback:(FIRAuthResultCallback)callback { + FIRSignInWithGameCenterRequest *request = + [[FIRSignInWithGameCenterRequest alloc] initWithPlayerID:credential.playerID + publicKeyURL:credential.publicKeyURL + signature:credential.signature + salt:credential.salt + timestamp:credential.timestamp + displayName:credential.displayName + requestConfiguration:_requestConfiguration]; + [FIRAuthBackend signInWithGameCenter:request + callback:^(FIRSignInWithGameCenterResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + if (callback) { + callback(nil, error); + } + return; + } + + [self completeSignInWithAccessToken:response.IDToken + accessTokenExpirationDate:response.approximateExpirationDate + refreshToken:response.refreshToken + anonymous:NO + callback:callback]; + }]; +} + /** @fn internalSignInWithEmail:link:completion: @brief Signs in using an email and email sign-in link. @param email The user's email address. @@ -704,6 +741,21 @@ - (void)internalSignInAndRetrieveDataWithCredential:(FIRAuthCredential *)credent return; } + if ([credential isKindOfClass:[FIRGameCenterAuthCredential class]]) { + // Special case for Game Center credentials. + [self signInWithGameCenterCredential:(FIRGameCenterAuthCredential *)credential + callback:^(FIRUser *_Nullable user, NSError *_Nullable error) { + if (callback) { + FIRAuthDataResult *result; + if (user) { + result = [[FIRAuthDataResult alloc] initWithUser:user additionalUserInfo:nil]; + } + callback(result, error); + } + }]; + return; + } + #if TARGET_OS_IOS if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) { // Special case for phone auth credentials diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h index 6fa276c6523..a8a937b7d6f 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h @@ -445,6 +445,12 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)missingAppTokenErrorWithUnderlyingError:(nullable NSError *)underlyingError; +/** @fn localPlayerNotAuthenticatedError + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeLocalPlayerNotAuthenticated code. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)localPlayerNotAuthenticatedError; + /** @fn notificationNotForwardedError @brief Constructs an @c NSError with the @c FIRAuthErrorCodeNotificationNotForwarded code. @return The NSError instance associated with the given FIRAuthError. diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m index f543dfe6e67..05bd867e053 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m @@ -311,6 +311,12 @@ static NSString *const kFIRAuthErrorMessageInvalidVerificationID = @"The verification ID used to create the phone auth credential is invalid."; +/** @var kFIRAuthErrorMessageLocalPlayerNotAuthenticated + @brief Message for @c FIRAuthErrorCodeLocalPlayerNotAuthenticated error code. + */ +static NSString *const kFIRAuthErrorMessageLocalPlayerNotAuthenticated = + @"The local player is not authenticated. Please log the local player in to Game Center."; + /** @var kFIRAuthErrorMessageSessionExpired @brief Message for @c FIRAuthErrorCodeSessionExpired error code. */ @@ -548,6 +554,8 @@ return kFIRAuthErrorMessageWebInternalError; case FIRAuthErrorCodeMalformedJWT: return kFIRAuthErrorMessageMalformedJWT; + case FIRAuthErrorCodeLocalPlayerNotAuthenticated: + return kFIRAuthErrorMessageLocalPlayerNotAuthenticated; } } @@ -673,6 +681,8 @@ return @"ERROR_WEB_INTERNAL_ERROR"; case FIRAuthErrorCodeMalformedJWT: return @"ERROR_MALFORMED_JWT"; + case FIRAuthErrorCodeLocalPlayerNotAuthenticated: + return @"ERROR_LOCAL_PLAYER_NOT_AUTHENTICATED"; } } @@ -986,6 +996,10 @@ + (NSError *)missingAppTokenErrorWithUnderlyingError:(nullable NSError *)underly underlyingError:underlyingError]; } ++ (NSError *)localPlayerNotAuthenticatedError { + return [self errorWithCode:FIRAuthInternalErrorCodeLocalPlayerNotAuthenticated]; +} + + (NSError *)notificationNotForwardedError { return [self errorWithCode:FIRAuthInternalErrorCodeNotificationNotForwarded]; } diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h index 7032c977f80..71a8fd0a5c9 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h @@ -370,6 +370,11 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeAppNotVerified = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeAppNotVerified, + /** Indicates that the Game Center local player was not authenticated. + */ + FIRAuthInternalErrorCodeLocalPlayerNotAuthenticated = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeLocalPlayerNotAuthenticated, + /** Indicates that a non-null user was expected as an argmument to the operation but a null user was provided. */ diff --git a/Firebase/Auth/Source/FIRAuthProvider.m b/Firebase/Auth/Source/FIRAuthProvider.m index 72a00ef84d5..8a08d9b08cd 100644 --- a/Firebase/Auth/Source/FIRAuthProvider.m +++ b/Firebase/Auth/Source/FIRAuthProvider.m @@ -39,6 +39,9 @@ // Declared 'extern' in FIRPhoneAuthProvider.h NSString *const FIRPhoneAuthProviderID = @"phone"; +// Declared 'extern' in FIRGameCenterAuthProvider.h +NSString *const FIRGameCenterAuthProviderID = @"gc.apple.com"; + #pragma mark - sign-in methods constants // Declared 'extern' in FIRGoogleAuthProvider.h @@ -61,3 +64,6 @@ // Declared 'extern' in FIRPhoneAuthProvider.h NSString *const FIRPhoneAuthSignInMethod = @"phone"; + +// Declared 'extern' in FIRGameCenterAuthProvider.h +NSString *const FIRGameCenterAuthSignInMethod = @"gc.apple.com"; diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m index 0dd43d16da0..4bfdc1a80a9 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/FIRUser.m @@ -14,10 +14,10 @@ * limitations under the License. */ -#import - #import "FIRUser_Internal.h" +#import + #import "FIRAdditionalUserInfo_Internal.h" #import "FIRAuth.h" #import "FIRAuthCredential_Internal.h" @@ -34,14 +34,16 @@ #import "FIRDeleteAccountResponse.h" #import "FIREmailAuthProvider.h" #import "FIREmailPasswordAuthCredential.h" +#import "FIRGameCenterAuthCredential.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" #import "FIRGetOOBConfirmationCodeRequest.h" #import "FIRGetOOBConfirmationCodeResponse.h" -#import #import "FIRSecureTokenService.h" #import "FIRSetAccountInfoRequest.h" #import "FIRSetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" #import "FIRUserInfoImpl.h" #import "FIRUserMetadata_Internal.h" #import "FIRVerifyAssertionRequest.h" @@ -204,6 +206,15 @@ - (nullable instancetype)initWithUser:(FIRUser *)user NS_DESIGNATED_INITIALIZER; @end +@interface FIRUser () + +/** @property anonymous + @brief Whether the current user is anonymous. + */ +@property(nonatomic, readwrite) BOOL anonymous; + +@end + @implementation FIRUser { /** @var _hasEmailPasswordCredential @brief Whether or not the user can be authenticated by using Firebase email and password. @@ -268,7 +279,7 @@ + (void)retrieveUserWithAuth:(FIRAuth *)auth callback(nil, error); return; } - user->_anonymous = anonymous; + user.anonymous = anonymous; [user updateWithGetAccountInfoResponse:response]; callback(user, nil); }]; @@ -344,7 +355,7 @@ - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:_userID forKey:kUserIDCodingKey]; - [aCoder encodeBool:_anonymous forKey:kAnonymousCodingKey]; + [aCoder encodeBool:self.anonymous forKey:kAnonymousCodingKey]; [aCoder encodeBool:_hasEmailPasswordCredential forKey:kHasEmailPasswordCredentialCodingKey]; [aCoder encodeObject:_providerData forKey:kProviderDataKey]; [aCoder encodeObject:_email forKey:kEmailCodingKey]; @@ -588,7 +599,7 @@ - (void)updateEmail:(nullable NSString *)email // Set the account to non-anonymous if there are any providers, even if // they're not email/password ones. if (userAccountInfo.providerUserInfo.count > 0) { - self->_anonymous = NO; + self.anonymous = NO; } for (FIRGetAccountInfoResponseProviderUserInfo *providerUserInfo in userAccountInfo.providerUserInfo) { @@ -679,7 +690,7 @@ - (void)internalUpdateOrLinkPhoneNumberCredential:(FIRPhoneAuthCredential *)phon completion(error); return; } - self->_anonymous = NO; + self.anonymous = NO; if (![self updateKeychain:&error]) { completion(error); return; @@ -1002,6 +1013,60 @@ - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential return; } + if ([credential isKindOfClass:[FIRGameCenterAuthCredential class]]) { + FIRGameCenterAuthCredential *gameCenterCredential = (FIRGameCenterAuthCredential *)credential; + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + FIRAuthRequestConfiguration *requestConfiguration = self.auth.requestConfiguration; + FIRSignInWithGameCenterRequest *gameCenterRequest = + [[FIRSignInWithGameCenterRequest alloc] initWithPlayerID:gameCenterCredential.playerID + publicKeyURL:gameCenterCredential.publicKeyURL + signature:gameCenterCredential.signature + salt:gameCenterCredential.salt + timestamp:gameCenterCredential.timestamp + displayName:gameCenterCredential.displayName + requestConfiguration:requestConfiguration]; + gameCenterRequest.accessToken = accessToken; + + [FIRAuthBackend signInWithGameCenter:gameCenterRequest + callback:^(FIRSignInWithGameCenterResponse *_Nullable response, + NSError *_Nullable error) { + if (error){ + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + } else { + [self internalGetTokenWithCallback:^(NSString *_Nullable accessToken, + NSError *_Nullable error) { + if (error) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + + FIRGetAccountInfoRequest *getAccountInfoRequest = + [[FIRGetAccountInfoRequest alloc] initWithAccessToken:accessToken + requestConfiguration:requestConfiguration]; + [FIRAuthBackend getAccountInfo:getAccountInfoRequest + callback:^(FIRGetAccountInfoResponse *_Nullable response, + NSError *_Nullable error) { + if (error) { + [self signOutIfTokenIsInvalidWithError:error]; + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + self.anonymous = NO; + [self updateWithGetAccountInfoResponse:response]; + if (![self updateKeychain:&error]) { + callInMainThreadWithAuthDataResultAndError(completion, nil, error); + return; + } + callInMainThreadWithAuthDataResultAndError(completion, result, nil); + }]; + }]; + } + }]; + }]; + return; + } + #if TARGET_OS_IOS if ([credential isKindOfClass:[FIRPhoneAuthCredential class]]) { FIRPhoneAuthCredential *phoneAuthCredential = (FIRPhoneAuthCredential *)credential; @@ -1070,7 +1135,7 @@ - (void)linkAndRetrieveDataWithCredential:(FIRAuthCredential *)credential completeWithError(nil, error); return; } - self->_anonymous = NO; + self.anonymous = NO; [self updateWithGetAccountInfoResponse:response]; if (![self updateKeychain:&error]) { completeWithError(nil, error); diff --git a/Firebase/Auth/Source/NSData+FIRBase64.h b/Firebase/Auth/Source/NSData+FIRBase64.h new file mode 100644 index 00000000000..114cbfd9a93 --- /dev/null +++ b/Firebase/Auth/Source/NSData+FIRBase64.h @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSData (FIRBase64) + +/** @fn fir_base64URLEncodedStringWithOptions: + @brief Get a web safe base64 encoded string + @param options The base64 encoding options + */ +- (NSString *)fir_base64URLEncodedStringWithOptions:(NSDataBase64EncodingOptions)options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/NSData+FIRBase64.m b/Firebase/Auth/Source/NSData+FIRBase64.m new file mode 100644 index 00000000000..173ec9bf227 --- /dev/null +++ b/Firebase/Auth/Source/NSData+FIRBase64.m @@ -0,0 +1,29 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "NSData+FIRBase64.h" + +@implementation NSData (FIRBase64) + +- (NSString *)fir_base64URLEncodedStringWithOptions:(NSDataBase64EncodingOptions)options { + NSString *string = [self base64EncodedStringWithOptions:options]; + string = [string stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; + string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"-"]; + string = [string stringByReplacingOccurrencesOfString:@"=" withString:@""]; + return string; +} + +@end diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 796c01314da..9d177b662d7 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -299,6 +299,10 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { */ FIRAuthErrorCodeWebInternalError = 17062, + /** Indicates that the local player was not authenticated prior to attempting Game Center signin. + */ + FIRAuthErrorCodeLocalPlayerNotAuthenticated = 17066, + /** Indicates that a non-null user was expected as an argmument to the operation but a null user was provided. */ diff --git a/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h b/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h new file mode 100644 index 00000000000..d49b8f6a505 --- /dev/null +++ b/Firebase/Auth/Source/Public/FIRGameCenterAuthProvider.h @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@class FIRAuthCredential; + +NS_ASSUME_NONNULL_BEGIN + +/** + @brief A string constant identifying the Game Center identity provider. + */ +extern NSString *const FIRGameCenterAuthProviderID NS_SWIFT_NAME(GameCenterAuthProviderID); + +/** + @brief A string constant identifying the Game Center sign-in method. + */ +extern NSString *const _Nonnull FIRGameCenterAuthSignInMethod +NS_SWIFT_NAME(GameCenterAuthSignInMethod); + +/** @typedef FIRGameCenterCredentialCallback + @brief The type of block invoked when the Game Center credential code has finished. + @param credential On success, the credential will be provided, nil otherwise. + @param error On error, the error that occured, nil otherwise. + */ +typedef void (^FIRGameCenterCredentialCallback)(FIRAuthCredential *_Nullable credential, + NSError *_Nullable error) +NS_SWIFT_NAME(GameCenterCredentialCallback); + +/** @class FIRGameCenterAuthProvider + @brief A concrete implementation of @c FIRAuthProvider for Game Center Sign In. + */ +NS_SWIFT_NAME(GameCenterAuthProvider) +@interface FIRGameCenterAuthProvider : NSObject + +/** @fn getCredentialWithCompletion: + @brief Creates a @c FIRAuthCredential for a Game Center sign in. + */ ++ (void)getCredentialWithCompletion:(FIRGameCenterCredentialCallback)completion +NS_SWIFT_NAME(getCredential(completion:)); + +/** @fn init + @brief This class is not meant to be initialized. + */ +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FirebaseAuth.h b/Firebase/Auth/Source/Public/FirebaseAuth.h index c8837f83d7a..15693fe9d09 100644 --- a/Firebase/Auth/Source/Public/FirebaseAuth.h +++ b/Firebase/Auth/Source/Public/FirebaseAuth.h @@ -26,6 +26,7 @@ #import "FirebaseAuthVersion.h" #import "FIREmailAuthProvider.h" #import "FIRFacebookAuthProvider.h" +#import "FIRGameCenterAuthProvider.h" #import "FIRGitHubAuthProvider.h" #import "FIRGoogleAuthProvider.h" #import "FIROAuthProvider.h" diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h index f4899d0c656..9ced6a37a80 100644 --- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.h +++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.h @@ -19,6 +19,8 @@ @class FIRAuthRequestConfiguration; @class FIRCreateAuthURIRequest; @class FIRCreateAuthURIResponse; +@class FIRDeleteAccountRequest; +@class FIRDeleteAccountResponse; @class FIREmailLinkSignInRequest; @class FIREmailLinkSignInResponse; @class FIRGetAccountInfoRequest; @@ -45,10 +47,11 @@ @class FIRVerifyPhoneNumberResponse; @class FIRSendVerificationCodeRequest; @class FIRSendVerificationCodeResponse; +@class FIRSignInWithGameCenterRequest; +@class FIRSignInWithGameCenterResponse; @class FIRSignUpNewUserRequest; @class FIRSignUpNewUserResponse; -@class FIRDeleteAccountRequest; -@class FIRDeleteAccountResponse; + @protocol FIRAuthBackendImplementation; @protocol FIRAuthBackendRPCIssuer; @@ -214,6 +217,15 @@ typedef void (^FIRVerifyPhoneNumberResponseCallback) typedef void (^FIRVerifyClientResponseCallback) (FIRVerifyClientResponse *_Nullable response, NSError *_Nullable error); +/** @typedef FIRSignInWithGameCenterResponseCallback + @brief The type of block used to return the result of a call to the SignInWithGameCenter endpoint. + @param response The received response, if any. + @param error The error which occurred, if any. + @remarks One of response or error will be non-nil. + */ +typedef void (^FIRSignInWithGameCenterResponseCallback) + (FIRSignInWithGameCenterResponse *_Nullable response, NSError *_Nullable error); + /** @class FIRAuthBackend @brief Simple static class with methods representing the backend RPCs. @remarks All callback blocks passed as method parameters are invoked asynchronously on the @@ -361,6 +373,15 @@ typedef void (^FIRVerifyClientResponseCallback) + (void)deleteAccount:(FIRDeleteAccountRequest *)request callback:(FIRDeleteCallBack)callback; +/** @fn SignInWithGameCenter:callback: + @brief Calls the SignInWithGameCenter endpoint, which is responsible for authenticating a user + who has Game Center credentials. + @param request The request parameters. + @param callback The callback. + */ ++ (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback; + #if TARGET_OS_IOS /** @fn sendVerificationCode:callback: @brief Calls the sendVerificationCode endpoint, which is responsible for sending the @@ -555,6 +576,15 @@ typedef void (^FIRVerifyClientResponseCallback) callback:(FIRVerifyClientResponseCallback)callback; #endif +/** @fn SignInWithGameCenter:callback: + @brief Calls the SignInWithGameCenter endpoint, which is responsible for authenticating a user + who has Game Center credentials. + @param request The request parameters. + @param callback The callback. + */ +- (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback; + /** @fn resetPassword:callback @brief Calls the resetPassword endpoint, which is responsible for resetting a user's password given an OOB code and new password. diff --git a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m index 3bdb0f97ad2..d254174ca97 100644 --- a/Firebase/Auth/Source/RPCs/FIRAuthBackend.m +++ b/Firebase/Auth/Source/RPCs/FIRAuthBackend.m @@ -14,10 +14,11 @@ * limitations under the License. */ -#import - #import "FIRAuthBackend.h" +#import +#import + #import "FIRAuthErrorUtils.h" #import "FIRAuthGlobalWorkQueue.h" #import "FirebaseAuth.h" @@ -29,6 +30,8 @@ #import "FIRDeleteAccountResponse.h" #import "FIRGetAccountInfoRequest.h" #import "FIRGetAccountInfoResponse.h" +#import "FIRSignInWithGameCenterRequest.h" +#import "FIRSignInWithGameCenterResponse.h" #import "FIRGetOOBConfirmationCodeRequest.h" #import "FIRGetOOBConfirmationCodeResponse.h" #import "FIRGetProjectConfigRequest.h" @@ -55,8 +58,6 @@ #import "FIREmailLinkSignInResponse.h" #import "FIRVerifyPhoneNumberRequest.h" #import "FIRVerifyPhoneNumberResponse.h" -#import -#import #if TARGET_OS_IOS #import "../AuthProviders/Phone/FIRPhoneAuthCredential_Internal.h" @@ -469,6 +470,11 @@ + (void)deleteAccount:(FIRDeleteAccountRequest *)request callback:(FIRDeleteCall [[self implementation] deleteAccount:request callback:callback]; } ++ (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback { + [[self implementation] signInWithGameCenter:request callback:callback]; +} + #if TARGET_OS_IOS + (void)sendVerificationCode:(FIRSendVerificationCodeRequest *)request callback:(FIRSendVerificationCodeResponseCallback)callback { @@ -761,6 +767,22 @@ - (void)resetPassword:(FIRResetPasswordRequest *)request }]; } +- (void)signInWithGameCenter:(FIRSignInWithGameCenterRequest *)request + callback:(FIRSignInWithGameCenterResponseCallback)callback { + FIRSignInWithGameCenterResponse *response = [[FIRSignInWithGameCenterResponse alloc] init]; + [self postWithRequest:request response:response callback:^(NSError *error) { + if (error) { + if (callback) { + callback(nil, error); + } + } else { + if (callback) { + callback(response, nil); + } + } + }]; +} + #pragma mark - Generic RPC handling methods /** @fn postWithRequest:response:callback: diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.h b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.h new file mode 100644 index 00000000000..52720cbab9c --- /dev/null +++ b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.h @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCRequest.h" +#import "FIRIdentityToolkitRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @class FIRSignInWithGameCenterRequest + @brief The request to sign in with Game Center account + */ +@interface FIRSignInWithGameCenterRequest : FIRIdentityToolkitRequest + +/** @property playerID + @brief The playerID to verify. + */ +@property(nonatomic, copy) NSString *playerID; + +/** @property publicKeyURL + @brief The URL for the public encryption key. + */ +@property(nonatomic, copy) NSURL *publicKeyURL; + +/** @property signature + @brief The verification signature data generated by Game Center. + */ +@property(nonatomic, copy) NSData *signature; + +/** @property salt + @brief A random strong used to compute the hash and keep it randomized. + */ +@property(nonatomic, copy) NSData *salt; + +/** @property timestamp + @brief The date and time that the signature was created. + */ +@property(nonatomic, assign) uint64_t timestamp; + +/** @property accessToken + @brief The STS Access Token for the authenticated user, only needed for linking the user. + */ +@property(nonatomic, copy, nullable) NSString *accessToken; + +/** @property displayName + @brief The display name of the local Game Center player. + */ +@property(nonatomic, copy, nullable) NSString *displayName; + +/** @fn initWithEndpoint:requestConfiguration: + @brief Please use initWithPlayerID:publicKeyURL:signature:salt:timestamp:requestConfiguration:. + */ +- (nullable instancetype)initWithEndpoint:(NSString *)endpoint + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_UNAVAILABLE; + +/** @fn initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName:requestConfiguration: + @brief Designated initializer. + @param playerID The ID of the Game Center player. + @param publicKeyURL The URL for the public encryption key. + @param signature The verification signature generated. + @param salt A random string used to compute the hash and keep it randomized. + @param timestamp The date and time that the signature was created. + @param displayName The display name of the Game Center player. + */ +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration + NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.m b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.m new file mode 100644 index 00000000000..35fb7541943 --- /dev/null +++ b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterRequest.m @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignInWithGameCenterRequest.h" + +#import "NSData+FIRBase64.h" + +NS_ASSUME_NONNULL_BEGIN + +/** @var kSignInWithGameCenterEndPoint + @brief The "SignInWithGameCenter" endpoint. + */ +static NSString *const kSignInWithGameCenterEndPoint = @"signInWithGameCenter"; + +@implementation FIRSignInWithGameCenterRequest + +- (nullable instancetype)initWithPlayerID:(NSString *)playerID + publicKeyURL:(NSURL *)publicKeyURL + signature:(NSData *)signature + salt:(NSData *)salt + timestamp:(uint64_t)timestamp + displayName:(NSString *)displayName + requestConfiguration:(FIRAuthRequestConfiguration *)requestConfiguration { + self = [super initWithEndpoint:kSignInWithGameCenterEndPoint + requestConfiguration:requestConfiguration]; + if (self) { + _playerID = playerID; + _publicKeyURL = [publicKeyURL copy]; + _signature = [signature copy]; + _salt = [salt copy]; + _timestamp = timestamp; + _displayName = displayName; + } + return self; +} + +#pragma mark - FIRAuthRPCRequest + +- (nullable id)unencodedHTTPRequestBodyWithError:(NSError *__autoreleasing _Nullable *)error { + NSMutableDictionary *postBody = [NSMutableDictionary dictionary]; + if (_playerID) { + postBody[@"playerId"] = _playerID; + } + if (_publicKeyURL) { + postBody[@"publicKeyUrl"] = _publicKeyURL.absoluteString; + } + if (_signature) { + postBody[@"signature"] = [_signature fir_base64URLEncodedStringWithOptions:0]; + } + if (_salt) { + postBody[@"salt"] = [_salt fir_base64URLEncodedStringWithOptions:0]; + } + if (_timestamp != 0) { + postBody[@"timestamp"] = [NSNumber numberWithUnsignedLongLong:_timestamp]; + } + if (_accessToken) { + postBody[@"idToken"] = _accessToken; + } + if (_displayName) { + postBody[@"displayName"] = _displayName; + } + return postBody; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.h b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.h new file mode 100644 index 00000000000..75dbd75a956 --- /dev/null +++ b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "FIRAuthRPCResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRSignInWithGameCenterResponse : NSObject + +/** @property IDToken + @breif Either an authorization code suitable for performing an STS token exchange, or the access + token from Secure Token Service, depending on whether @c returnSecureToken is set on the + request. + */ +@property(nonatomic, copy, readonly, nullable) NSString *IDToken; + +/** @property refreshToken + @breif @breif The refresh token from Secure Token Service. + */ +@property(nonatomic, copy, readonly, nullable) NSString *refreshToken; + +/** @property localID + @breif @breif The Firebase Auth user ID. + */ +@property(nonatomic, copy, readonly, nullable) NSString *localID; + +/** @property playerID + @breif @breif The verified player ID. + */ +@property(nonatomic, copy, readonly, nullable) NSString *playerID; + +/** @property approximateExpirationDate + @breif The approximate expiration date of the access token. + */ +@property(nonatomic, copy, readonly, nullable) NSDate *approximateExpirationDate; + +/** @property isNewUser + @breif Flag indicating that the user signing in is a new user and not a returning user. + */ +@property(nonatomic, assign) BOOL isNewUser; + +/** @property displayName + @breif The user's Game Center display name. + */ +@property(nonatomic, copy, readonly, nullable) NSString *displayName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.m b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.m new file mode 100644 index 00000000000..7cd1b9a6dd5 --- /dev/null +++ b/Firebase/Auth/Source/RPCs/FIRSignInWithGameCenterResponse.m @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "FIRSignInWithGameCenterResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSignInWithGameCenterResponse + +- (BOOL)setWithDictionary:(NSDictionary *)dictionary + error:(NSError *_Nullable *_Nullable)error { + _IDToken = [dictionary[@"idToken"] copy]; + _refreshToken = [dictionary[@"refreshToken"] copy]; + _localID = [dictionary[@"localId"] copy]; + _approximateExpirationDate = nil; + if ([dictionary[@"expiresIn"] isKindOfClass:[NSString class]]) { + _approximateExpirationDate = [NSDate dateWithTimeIntervalSinceNow:[dictionary[@"expiresIn"] integerValue]]; + } + _playerID = [dictionary[@"playerId"] copy]; + _isNewUser = [dictionary[@"isNewUser"] boolValue]; + _displayName = [dictionary[@"displayName"] copy]; + return YES; +} + +@end + +NS_ASSUME_NONNULL_END