From 7eb4de50b38c7fdf9ed0d3780987619a81007f0d Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Mon, 26 Jul 2021 15:42:21 -0700 Subject: [PATCH 1/7] Add support for Facebook Limited Login. --- .../Sources/FUIFacebookAuth.m | 142 ++++++++++++++---- .../FirebaseFacebookAuthUI/FUIFacebookAuth.h | 5 + 2 files changed, 121 insertions(+), 26 deletions(-) diff --git a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m index d34548007ad..a725edf7a1f 100644 --- a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m +++ b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m @@ -20,11 +20,13 @@ #import #if SWIFT_PACKAGE +@import CommonCrypto; @import FBSDKCoreKit; @import FBSDKLoginKit; #else #import #import +#import #endif // SWIFT_PACKAGE /** @var kTableName @@ -86,6 +88,11 @@ @implementation FUIFacebookAuth { @brief The email address associated with this account. */ NSString *_email; + + /** @var _currentNonce + @brief The current nonce for a Facebook Limited Login sign-in attempt. + */ + NSString *_currentNonce; } + (NSBundle *)bundle { @@ -191,29 +198,59 @@ - (void)signInWithDefaultValue:(nullable NSString *)defaultValue return; } - [_loginManager logInWithPermissions:_scopes - fromViewController:presentingViewController - handler:^(FBSDKLoginManagerLoginResult *result, - NSError *error) { - if (error) { - NSError *newError = - [FUIAuthErrorUtils providerErrorWithUnderlyingError:error - providerID:FIRFacebookAuthProviderID]; - [self completeSignInFlowWithAccessToken:nil error:newError]; - } else if (result.isCancelled) { - NSError *newError = [FUIAuthErrorUtils userCancelledSignInError]; - [self completeSignInFlowWithAccessToken:nil error:newError]; - } else { - // Retrieve email. - [[[FBSDKGraphRequest alloc] initWithGraphPath:@"me" parameters:@{ @"fields" : @"email" }] startWithCompletion:^(id connection, - id result, - NSError *error) { - self->_email = result[@"email"]; - }]; - [self completeSignInFlowWithAccessToken:result.token.tokenString - error:nil]; - } - }]; + if (self.useLimitedLogin) { + // Facebook Limited Login + NSString *nonce = [self randomNonce]; + self->_currentNonce = nonce; + FBSDKLoginConfiguration *configuration = + [[FBSDKLoginConfiguration alloc] initWithPermissions:_scopes + tracking:FBSDKLoginTrackingLimited + nonce:[self stringBySha256HashingString:nonce]]; + [_loginManager logInFromViewController:presentingViewController + configuration:configuration + completion:^(FBSDKLoginManagerLoginResult *result, NSError *error) { + if (error) { + NSError *newError = + [FUIAuthErrorUtils providerErrorWithUnderlyingError:error + providerID:FIRFacebookAuthProviderID]; + [self completeSignInFlowWithAccessToken:nil idToken:nil error:newError]; + } else if (result.isCancelled) { + NSError *newError = [FUIAuthErrorUtils userCancelledSignInError]; + [self completeSignInFlowWithAccessToken:nil idToken:nil error:newError]; + } else { + self->_email = FBSDKProfile.currentProfile.email; + NSString *idToken = FBSDKAuthenticationToken.currentAuthenticationToken.tokenString; + [self completeSignInFlowWithAccessToken:nil idToken:idToken error:nil]; + } + }]; + } else { + [_loginManager logInWithPermissions:_scopes + fromViewController:presentingViewController + handler:^(FBSDKLoginManagerLoginResult *result, + NSError *error) { + if (error) { + NSError *newError = + [FUIAuthErrorUtils providerErrorWithUnderlyingError:error + providerID:FIRFacebookAuthProviderID]; + [self completeSignInFlowWithAccessToken:nil idToken:nil error:newError]; + } else if (result.isCancelled) { + NSError *newError = [FUIAuthErrorUtils userCancelledSignInError]; + [self completeSignInFlowWithAccessToken:nil idToken:nil error:newError]; + } else { + // Retrieve email. + [[[FBSDKGraphRequest alloc] initWithGraphPath:@"me" + parameters:@{ @"fields" : @"email" }] + startWithCompletion:^(id connection, + id result, + NSError *error) { + self->_email = result[@"email"]; + }]; + [self completeSignInFlowWithAccessToken:result.token.tokenString + idToken:nil + error:nil]; + } + }]; + } } - (void)signInWithOAuthProvider:(FIROAuthProvider *)oauthProvider @@ -272,18 +309,28 @@ - (BOOL)handleOpenURL:(NSURL *)URL sourceApplication:(NSString *)sourceApplicati /** @fn completeSignInFlowWithAccessToken:error: @brief Called with the result of a Facebook sign-in attempt. Invokes and clears any pending sign in callback block. - @param accessToken The Facebook access token, if successful. + @param accessToken The Facebook access token, if the Facebook sign-in attempt with tracking enabled is successful. + @param idToken The Facebook ID token, if the Facebook Limited Login attempt is successful. @param error An error which occurred during the sign-in attempt. */ - (void)completeSignInFlowWithAccessToken:(nullable NSString *)accessToken + idToken:(nullable NSString *)idToken error:(nullable NSError *)error { if (error) { [self callbackWithCredential:nil error:error result:nil]; return; } - // Assume accessToken cannot be nil if there's no error. + FIRAuthCredential *credential; + if (idToken) { + NSString *rawNonce = self->_currentNonce; + credential = [FIROAuthProvider credentialWithProviderID:FIRFacebookAuthProviderID + IDToken:idToken + rawNonce:rawNonce]; + } else { + // Assume accessToken cannot be nil if there's no error and idToken is nil. NSString *_Nonnull token = (id _Nonnull)accessToken; - FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:token]; + credential = [FIRFacebookAuthProvider credentialWithAccessToken:token]; + } UIActivityIndicatorView *activityView = [FUIAuthBaseViewController addActivityIndicator:_presentingViewController.view]; [activityView startAnimating]; @@ -347,4 +394,47 @@ - (FBSDKLoginManager *)createLoginManager { return [[FBSDKLoginManager alloc] init]; } +- (NSString *)randomNonce { + NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._"; + NSMutableString *result = [NSMutableString string]; + NSInteger remainingLength = 32; + + while (remainingLength > 0) { + NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16]; + for (NSInteger i = 0; i < 16; i++) { + uint8_t random = 0; + int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random); + NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode); + + [randoms addObject:@(random)]; + } + + for (NSNumber *random in randoms) { + if (remainingLength == 0) { + break; + } + + if (random.unsignedIntValue < characterSet.length) { + unichar character = [characterSet characterAtIndex:random.unsignedIntValue]; + [result appendFormat:@"%C", character]; + remainingLength--; + } + } + } + + return result; +} + +- (NSString *)stringBySha256HashingString:(NSString *)input { + const char *string = [input UTF8String]; + unsigned char result[CC_SHA256_DIGEST_LENGTH]; + CC_SHA256(string, (CC_LONG)strlen(string), result); + + NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; + for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { + [hashed appendFormat:@"%02x", result[i]]; + } + return hashed; +} + @end diff --git a/FirebaseFacebookAuthUI/Sources/Public/FirebaseFacebookAuthUI/FUIFacebookAuth.h b/FirebaseFacebookAuthUI/Sources/Public/FirebaseFacebookAuthUI/FUIFacebookAuth.h index a425f13b970..ae088d38f8f 100644 --- a/FirebaseFacebookAuthUI/Sources/Public/FirebaseFacebookAuthUI/FUIFacebookAuth.h +++ b/FirebaseFacebookAuthUI/Sources/Public/FirebaseFacebookAuthUI/FUIFacebookAuth.h @@ -48,6 +48,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property(nonatomic, readwrite) FUIButtonAlignment buttonAlignment; +/** @property useLimitedLogin + @brief Whether or not Facebook Login should use Limited Login mode. + */ +@property(nonatomic, assign) BOOL useLimitedLogin; + /** @fn initWithAuthUI @brief Convenience initializer. Uses a default permission of `@[ "email" ]`. @param authUI The @c FUIAuth instance that manages this provider. From dac3ee1daac1208fbf0e937bc2dd1f53f6861abb Mon Sep 17 00:00:00 2001 From: Rosalyn Tan Date: Thu, 29 Jul 2021 11:32:48 -0400 Subject: [PATCH 2/7] Address review comment to use shared utility method for nonce and hashing. --- .../Sources/FUIFacebookAuth.m | 63 +++---------------- FirebaseOAuthUI/Sources/FUIOAuth.m | 3 + 2 files changed, 12 insertions(+), 54 deletions(-) diff --git a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m index a725edf7a1f..e3a4ec9e1df 100644 --- a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m +++ b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m @@ -20,13 +20,11 @@ #import #if SWIFT_PACKAGE -@import CommonCrypto; @import FBSDKCoreKit; @import FBSDKLoginKit; #else #import #import -#import #endif // SWIFT_PACKAGE /** @var kTableName @@ -70,6 +68,11 @@ @interface FUIFacebookAuth () */ @property(nonatomic, strong) FIROAuthProvider *providerForEmulator; +/** @property currentNonce + @brief The nonce for the current Facebook Limited Login session, if any. + */ +@property(nonatomic, copy, nullable) NSString *currentNonce; + @end @implementation FUIFacebookAuth { @@ -88,11 +91,6 @@ @implementation FUIFacebookAuth { @brief The email address associated with this account. */ NSString *_email; - - /** @var _currentNonce - @brief The current nonce for a Facebook Limited Login sign-in attempt. - */ - NSString *_currentNonce; } + (NSBundle *)bundle { @@ -200,12 +198,12 @@ - (void)signInWithDefaultValue:(nullable NSString *)defaultValue if (self.useLimitedLogin) { // Facebook Limited Login - NSString *nonce = [self randomNonce]; - self->_currentNonce = nonce; + NSString *nonce = [FUIAuthUtils randomNonce]; + self.currentNonce = nonce; FBSDKLoginConfiguration *configuration = [[FBSDKLoginConfiguration alloc] initWithPermissions:_scopes tracking:FBSDKLoginTrackingLimited - nonce:[self stringBySha256HashingString:nonce]]; + nonce:[FUIAuthUtils stringBySHA256HashingString:nonce]]; [_loginManager logInFromViewController:presentingViewController configuration:configuration completion:^(FBSDKLoginManagerLoginResult *result, NSError *error) { @@ -322,7 +320,7 @@ - (void)completeSignInFlowWithAccessToken:(nullable NSString *)accessToken } FIRAuthCredential *credential; if (idToken) { - NSString *rawNonce = self->_currentNonce; + NSString *rawNonce = self.currentNonce; credential = [FIROAuthProvider credentialWithProviderID:FIRFacebookAuthProviderID IDToken:idToken rawNonce:rawNonce]; @@ -394,47 +392,4 @@ - (FBSDKLoginManager *)createLoginManager { return [[FBSDKLoginManager alloc] init]; } -- (NSString *)randomNonce { - NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._"; - NSMutableString *result = [NSMutableString string]; - NSInteger remainingLength = 32; - - while (remainingLength > 0) { - NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16]; - for (NSInteger i = 0; i < 16; i++) { - uint8_t random = 0; - int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random); - NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode); - - [randoms addObject:@(random)]; - } - - for (NSNumber *random in randoms) { - if (remainingLength == 0) { - break; - } - - if (random.unsignedIntValue < characterSet.length) { - unichar character = [characterSet characterAtIndex:random.unsignedIntValue]; - [result appendFormat:@"%C", character]; - remainingLength--; - } - } - } - - return result; -} - -- (NSString *)stringBySha256HashingString:(NSString *)input { - const char *string = [input UTF8String]; - unsigned char result[CC_SHA256_DIGEST_LENGTH]; - CC_SHA256(string, (CC_LONG)strlen(string), result); - - NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; - for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { - [hashed appendFormat:@"%02x", result[i]]; - } - return hashed; -} - @end diff --git a/FirebaseOAuthUI/Sources/FUIOAuth.m b/FirebaseOAuthUI/Sources/FUIOAuth.m index c349ff49f7d..056fdaa8b96 100644 --- a/FirebaseOAuthUI/Sources/FUIOAuth.m +++ b/FirebaseOAuthUI/Sources/FUIOAuth.m @@ -99,6 +99,9 @@ @interface FUIOAuth () Date: Thu, 29 Jul 2021 14:47:33 -0400 Subject: [PATCH 3/7] Fix and update demo app. --- .../Sources/FUIFacebookAuth.m | 8 ++--- .../FirebaseUI-demo-objc/FUIAppDelegate.m | 6 ++++ .../Resources/Main.storyboard | 31 ++++++++++++++----- .../Samples/Auth/FUIAuthViewController.m | 8 +++-- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m index e3a4ec9e1df..df6541cb8f4 100644 --- a/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m +++ b/FirebaseFacebookAuthUI/Sources/FUIFacebookAuth.m @@ -304,7 +304,7 @@ - (BOOL)handleOpenURL:(NSURL *)URL sourceApplication:(NSString *)sourceApplicati #pragma mark - -/** @fn completeSignInFlowWithAccessToken:error: +/** @fn completeSignInFlowWithAccessToken:idToken:error: @brief Called with the result of a Facebook sign-in attempt. Invokes and clears any pending sign in callback block. @param accessToken The Facebook access token, if the Facebook sign-in attempt with tracking enabled is successful. @@ -325,9 +325,9 @@ - (void)completeSignInFlowWithAccessToken:(nullable NSString *)accessToken IDToken:idToken rawNonce:rawNonce]; } else { - // Assume accessToken cannot be nil if there's no error and idToken is nil. - NSString *_Nonnull token = (id _Nonnull)accessToken; - credential = [FIRFacebookAuthProvider credentialWithAccessToken:token]; + // Assume accessToken cannot be nil if there's no error and idToken is nil. + NSString *_Nonnull token = (id _Nonnull)accessToken; + credential = [FIRFacebookAuthProvider credentialWithAccessToken:token]; } UIActivityIndicatorView *activityView = [FUIAuthBaseViewController addActivityIndicator:_presentingViewController.view]; diff --git a/samples/objc/FirebaseUI-demo-objc/FUIAppDelegate.m b/samples/objc/FirebaseUI-demo-objc/FUIAppDelegate.m index 7b5bc915b49..4b45cb8f811 100644 --- a/samples/objc/FirebaseUI-demo-objc/FUIAppDelegate.m +++ b/samples/objc/FirebaseUI-demo-objc/FUIAppDelegate.m @@ -18,6 +18,7 @@ @import Firebase; @import FirebaseAuthUI; +@import FBSDKCoreKit; #import @implementation FUIAppDelegate @@ -26,6 +27,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [FIRApp configure]; [GTMSessionFetcher setLoggingEnabled:YES]; + [[FBSDKApplicationDelegate sharedInstance] application:application + didFinishLaunchingWithOptions:launchOptions]; return YES; } @@ -33,6 +36,9 @@ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { NSString *sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey]; + [[FBSDKApplicationDelegate sharedInstance] application:app + openURL:url + options:options]; return [self handleOpenUrl:url sourceApplication:sourceApplication]; } diff --git a/samples/objc/FirebaseUI-demo-objc/Resources/Main.storyboard b/samples/objc/FirebaseUI-demo-objc/Resources/Main.storyboard index 2502ad19938..7e3485f5983 100644 --- a/samples/objc/FirebaseUI-demo-objc/Resources/Main.storyboard +++ b/samples/objc/FirebaseUI-demo-objc/Resources/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -162,7 +162,7 @@ @@ -191,7 +191,7 @@ @@ -225,14 +225,14 @@ - + + + + + @@ -537,6 +548,7 @@ + @@ -561,7 +573,7 @@ - @@ -197,7 +198,7 @@ - + @@ -313,6 +314,17 @@ + + + + + @@ -508,6 +520,7 @@ + @@ -534,7 +547,7 @@ -