Эх сурвалжийг харах

Add multi-tenancy support to generic OAuth providers. (#9100)

* Add multi-tenancy support to generic OAuth providers.

* Update unit tests.

* Update changelog.

* Link issue instead of PR in changelog.
Rosalyn Tan 4 жил өмнө
parent
commit
944416b38b

+ 2 - 1
FirebaseAuth/CHANGELOG.md

@@ -1,5 +1,6 @@
 # Unreleased
-- [changed] Added a `X-Firebase-GMPID` header to network requests.
+- [changed] Added a `X-Firebase-GMPID` header to network requests. (#9046)
+- [fixed] Added multi-tenancy support to generic OAuth providers. (#7990)
 
 # 8.9.0
 - [changed] Improved error logging. (#8704)

+ 4 - 0
FirebaseAuth/Sources/AuthProvider/OAuth/FIROAuthProvider.m

@@ -308,6 +308,7 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                                      NSString *appID = strongSelf->_auth.app.options.googleAppID;
                                      NSString *apiKey =
                                          strongSelf->_auth.requestConfiguration.APIKey;
+                                     NSString *tenantID = strongSelf->_auth.tenantID;
                                      NSMutableDictionary *urlArguments = [@{
                                        @"apiKey" : apiKey,
                                        @"authType" : kAuthTypeSignInWithRedirect,
@@ -322,6 +323,9 @@ static NSString *const kCustomUrlSchemePrefix = @"app-";
                                      } else {
                                        urlArguments[@"appId"] = appID;
                                      }
+                                     if (tenantID) {
+                                       urlArguments[@"tid"] = tenantID;
+                                     }
                                      if (strongSelf.scopes.count) {
                                        urlArguments[@"scopes"] =
                                            [strongSelf.scopes componentsJoinedByString:@","];

+ 118 - 0
FirebaseAuth/Tests/Unit/FIROAuthProviderTests.m

@@ -102,6 +102,11 @@ static NSString *const kFakeFirebaseAppID = @"1:123456789:ios:123abc456def";
  */
 static NSString *const kFakeEncodedFirebaseAppID = @"app-1-123456789-ios-123abc456def";
 
+/** @var kFakeTenantID
+    @brief A fake tenant ID.
+ */
+static NSString *const kFakeTenantID = @"tenantID";
+
 /** @var kFakeOAuthResponseURL
     @brief A fake OAuth response URL used in test.
  */
@@ -313,6 +318,111 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
+        // `callbackMatcher` is at index 4
+        [invocation getArgument:&unretainedArgument atIndex:4];
+        FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
+        NSMutableString *redirectURL = [NSMutableString
+            stringWithString:[kFakeReverseClientID
+                                 stringByAppendingString:kFakeRedirectURLResponseURL]];
+        // Add fake OAuthResponse to callback.
+        [redirectURL appendString:kFakeOAuthResponseURL];
+        // Verify that the URL is rejected by the callback matcher without the event ID.
+        XCTAssertFalse(callbackMatcher([NSURL URLWithString:redirectURL]));
+        [redirectURL appendString:@"%26eventId%3D"];
+        [redirectURL appendString:params[@"eventId"]];
+        NSURLComponents *originalComponents = [[NSURLComponents alloc] initWithString:redirectURL];
+        // Verify that the URL is accepted by the callback matcher with the matching event ID.
+        XCTAssertTrue(callbackMatcher([originalComponents URL]));
+        NSURLComponents *components = [originalComponents copy];
+        components.query = @"https";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.host = @"badhost";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.path = @"badpath";
+        XCTAssertFalse(callbackMatcher([components URL]));
+        components = [originalComponents copy];
+        components.query = @"badquery";
+        XCTAssertFalse(callbackMatcher([components URL]));
+
+        // `completion` is at index 5
+        [invocation getArgument:&unretainedArgument atIndex:5];
+        FIRAuthURLPresentationCompletion completion = unretainedArgument;
+        dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+          completion(originalComponents.URL, nil);
+        });
+      });
+
+  XCTestExpectation *expectation = [self expectationWithDescription:@"callback"];
+  [_provider
+      getCredentialWithUIDelegate:mockUIDelegate
+                       completion:^(FIRAuthCredential *_Nullable credential,
+                                    NSError *_Nullable error) {
+                         XCTAssertTrue([NSThread isMainThread]);
+                         XCTAssertNil(error);
+                         XCTAssertTrue([credential isKindOfClass:[FIROAuthCredential class]]);
+                         FIROAuthCredential *OAuthCredential = (FIROAuthCredential *)credential;
+                         XCTAssertEqualObjects(kFakeOAuthResponseURL,
+                                               OAuthCredential.OAuthResponseURLString);
+                         [expectation fulfill];
+                       }];
+  [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil];
+  OCMVerifyAll(_mockBackend);
+}
+
+/** @fn testGetCredentialWithUIDelegateWithTenantID
+    @brief Tests a successful invocation of @c getCredentialWithUIDelegte:completion:
+ */
+- (void)testGetCredentialWithUIDelegateWithTenantID {
+  id mockBundle = OCMClassMock([NSBundle class]);
+  OCMStub(ClassMethod([mockBundle mainBundle])).andReturn(mockBundle);
+  OCMStub([mockBundle objectForInfoDictionaryKey:@"CFBundleURLTypes"]).andReturn(@[
+    @{@"CFBundleURLSchemes" : @[ kFakeReverseClientID ]}
+  ]);
+  OCMStub([mockBundle bundleIdentifier]).andReturn(kFakeBundleID);
+  OCMStub([_mockAuth tenantID]).andReturn(kFakeTenantID);
+
+  OCMStub([_mockOptions clientID]).andReturn(kFakeClientID);
+  _provider = [FIROAuthProvider providerWithProviderID:kFakeProviderID auth:_mockAuth];
+
+  OCMExpect([_mockBackend getProjectConfig:[OCMArg any] callback:[OCMArg any]])
+      .andCallBlock2(
+          ^(FIRGetProjectConfigRequest *request, FIRGetProjectConfigResponseCallback callback) {
+            XCTAssertNotNil(request);
+            dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
+              id mockGetProjectConfigResponse = OCMClassMock([FIRGetProjectConfigResponse class]);
+              OCMStub([mockGetProjectConfigResponse authorizedDomains]).andReturn(@[
+                kFakeAuthorizedDomain
+              ]);
+              callback(mockGetProjectConfigResponse, nil);
+            });
+          });
+
+  id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate));
+
+  // Expect view controller presentation by UIDelegate.
+  OCMExpect([_mockURLPresenter presentURL:OCMOCK_ANY
+                               UIDelegate:mockUIDelegate
+                          callbackMatcher:OCMOCK_ANY
+                               completion:OCMOCK_ANY])
+      .andDo(^(NSInvocation *invocation) {
+        __unsafe_unretained id unretainedArgument;
+        // Indices 0 and 1 indicate the hidden arguments self and _cmd.
+        // `presentURL` is at index 2.
+        [invocation getArgument:&unretainedArgument atIndex:2];
+        NSURL *presentURL = unretainedArgument;
+        XCTAssertEqualObjects(presentURL.scheme, @"https");
+        XCTAssertEqualObjects(presentURL.host, kFakeAuthorizedDomain);
+        XCTAssertEqualObjects(presentURL.path, @"/__/auth/handler");
+        NSDictionary *params = [FIRAuthWebUtils dictionaryWithHttpArgumentsString:presentURL.query];
+        XCTAssertEqualObjects(params[@"ibi"], kFakeBundleID);
+        XCTAssertEqualObjects(params[@"clientId"], kFakeClientID);
+        XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
+        XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
+        XCTAssertEqualObjects(params[@"tid"], kFakeTenantID);
+        XCTAssertNotNil(params[@"v"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -416,6 +526,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -516,6 +627,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -614,6 +726,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -713,6 +826,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -812,6 +926,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -910,6 +1025,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -1014,6 +1130,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;
@@ -1108,6 +1225,7 @@ static NSString *const kUnknownErrorString =
         XCTAssertEqualObjects(params[@"apiKey"], kFakeAPIKey);
         XCTAssertEqualObjects(params[@"authType"], @"signInWithRedirect");
         XCTAssertNotNil(params[@"v"]);
+        XCTAssertNil(params[@"tid"]);
         // `callbackMatcher` is at index 4
         [invocation getArgument:&unretainedArgument atIndex:4];
         FIRAuthURLCallbackMatcher callbackMatcher = unretainedArgument;