| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // Copyright 2021 Google LLC
- //
- // 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 Foundation
- import Combine
- import XCTest
- import FirebaseAuth
- class OAuthProviderTests: XCTestCase {
- override class func setUp() {
- FirebaseApp.configureForTests()
- Bundle.mock(with: MockBundle.self)
- }
- override class func tearDown() {
- FirebaseApp.app()?.delete { success in
- if success {
- print("Shut down app successfully.")
- } else {
- print("💥 There was a problem when shutting down the app..")
- }
- }
- }
- static let encodedFirebaseAppID = "app-1-1085102361755-ios-f790a919483d5bdf"
- static let reverseClientID = "com.googleusercontent.apps.123456"
- static let providerID = "fakeProviderID"
- static let authorizedDomain = "test.firebaseapp.com"
- static let oauthResponseURL = "fakeOAuthResponseURL"
- static let redirectURLResponseURL =
- "://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3FauthType%3DsignInWithRedirect%26link%3D"
- static let redirectURLBaseErrorString =
- "com.googleusercontent.apps.123456://firebaseauth/link?deep_link_id=https%3A%2F%2Fexample.firebaseapp.com%2F__%2Fauth%2Fcallback%3f"
- static let networkRequestFailedErrorString =
- "firebaseError%3D%257B%2522code%2522%253A%2522auth%252Fnetwork-request-failed%2522%252C%2522message%2522%253A%2522The%2520network%2520request%2520failed%2520.%2522%257D%26authType%3DsignInWithRedirect"
- static let internalErrorString =
- "firebaseError%3D%257B%2522code%2522%253A%2522auth%252Finternal-error%2522%252C%2522message%2522%253A%2522Internal%2520error%2520.%2522%257D%26authType%3DsignInWithRedirect"
- static let invalidClientIDString =
- "firebaseError%3D%257B%2522code%2522%253A%2522auth%252Finvalid-oauth-client-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect"
- static let unknownErrorString =
- "firebaseError%3D%257B%2522code%2522%253A%2522auth%252Funknown-error-id%2522%252C%2522message%2522%253A%2522The%2520OAuth%2520client%2520ID%2520provided%2520is%2520either%2520invalid%2520or%2520does%2520not%2520match%2520the%2520specified%2520API%2520key.%2522%257D%26authType%3DsignInWithRedirect"
- class MockAuth: Auth {
- private var _authURLPresenter: FIRAuthURLPresenter!
- override class func auth() -> Auth {
- let auth = MockAuth(
- apiKey: Credentials.apiKey,
- appName: "app1",
- appID: Credentials.googleAppID
- )!
- auth._authURLPresenter = MockAuthURLPresenter()
- return auth
- }
- override var app: FirebaseApp? {
- FirebaseApp.appForAuthUnitTestsWithName(name: "app1")
- }
- override var authURLPresenter: FIRAuthURLPresenter { _authURLPresenter }
- override var requestConfiguration: FIRAuthRequestConfiguration {
- MockRequestConfiguration(apiKey: Credentials.apiKey, appID: Credentials.googleAppID)!
- }
- }
- class MockRequestConfiguration: FIRAuthRequestConfiguration {}
- class MockUIDelegate: NSObject, AuthUIDelegate {
- func present(_ viewControllerToPresent: UIViewController,
- animated flag: Bool, completion: (() -> Void)? = nil) {}
- func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {}
- }
- class MockAuthURLPresenter: FIRAuthURLPresenter {
- var authURLPresentationResult: Result<URL, Error>?
- var redirectURL = reverseClientID + redirectURLResponseURL + oauthResponseURL
- override func present(_ URL: URL, uiDelegate UIDelegate: AuthUIDelegate?,
- callbackMatcher: @escaping FIRAuthURLCallbackMatcher,
- completion: @escaping FIRAuthURLPresentationCompletion) {
- XCTAssertEqual(URL.scheme, "https")
- XCTAssertEqual(URL.host, OAuthProviderTests.authorizedDomain)
- XCTAssertEqual(URL.path, "/__/auth/handler")
- do {
- let query = try XCTUnwrap(URL.query)
- let params = FIRAuthWebUtils.dictionary(withHttpArgumentsString: query)
- let ibi = try XCTUnwrap(params["ibi"] as? String)
- XCTAssertEqual(ibi, Credentials.bundleID)
- let clientId = try XCTUnwrap(params["clientId"] as? String)
- XCTAssertEqual(clientId, Credentials.clientID)
- let apiKey = try XCTUnwrap(params["apiKey"] as? String)
- XCTAssertEqual(apiKey, Credentials.apiKey)
- let authType = try XCTUnwrap(params["authType"] as? String)
- XCTAssertEqual(authType, "signInWithRedirect")
- XCTAssertNotNil(params["v"])
- // Verify that the URL is rejected by the callback matcher without the event ID.
- XCTAssertFalse(callbackMatcher(Foundation.URL(string: redirectURL)))
- redirectURL.append("%26eventId%3D")
- let eventId = try XCTUnwrap(params["eventId"] as? String)
- redirectURL.append(eventId)
- let originalComponents = URLComponents(string: redirectURL)
- // Verify that the URL is accepted by the callback matcher with the matching event ID.
- XCTAssertTrue(callbackMatcher(originalComponents?.url))
- var components = originalComponents
- components?.query = "https"
- XCTAssertFalse(callbackMatcher(components?.url))
- components = originalComponents
- components?.host = "badhost"
- XCTAssertFalse(callbackMatcher(components?.url))
- components = originalComponents
- components?.path = "badpath"
- XCTAssertFalse(callbackMatcher(components?.url))
- components = originalComponents
- components?.query = "badquery"
- XCTAssertFalse(callbackMatcher(components?.url))
- FIRAuthGlobalWorkQueue().async { [weak self] in
- switch self?.authURLPresentationResult {
- case let .success(url):
- completion(url, nil)
- case let .failure(error):
- completion(nil, error)
- default:
- completion(originalComponents?.url, nil)
- }
- }
- } catch {
- XCTFail("💥 Expect non-nil: \(error)")
- }
- }
- }
- class MockGetProjectConfigResponse: FIRGetProjectConfigResponse {
- override var authorizedDomains: [Any]? { [authorizedDomain] }
- }
- class MockAuthBackend: AuthBackendImplementationMock {
- override func getProjectConfig(_ request: FIRGetProjectConfigRequest,
- callback: @escaping FIRGetProjectConfigResponseCallback) {
- XCTAssertNotNil(request)
- FIRAuthGlobalWorkQueue().async {
- callback(MockGetProjectConfigResponse(), nil)
- }
- }
- }
- class MockBundle: Bundle {
- override class var main: Bundle { MockBundle() }
- override var bundleIdentifier: String? { Credentials.bundleID }
- override func object(forInfoDictionaryKey key: String) -> Any? {
- switch key {
- case "CFBundleURLTypes":
- return [["CFBundleURLSchemes": [reverseClientID]]]
- default:
- return nil
- }
- }
- }
- func testGetCredentialWithUIDelegateWithClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- switch completion {
- case .finished:
- print("Finished")
- case let .failure(error):
- XCTFail("💥 Something went wrong: \(error)")
- }
- } receiveValue: { credential in
- do {
- XCTAssertTrue(Thread.isMainThread)
- let oauthCredential = try XCTUnwrap(credential as? OAuthCredential)
- XCTAssertEqual(Self.oauthResponseURL, oauthCredential.oAuthResponseURLString)
- } catch {
- XCTFail("💥 Expect non-nil OAuth credential: \(error)")
- }
- getCredentialExpectation.fulfill()
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- func testGetCredentialWithUIDelegateUserCancellationWithClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let authURLPresenter = auth.authURLPresenter as? MockAuthURLPresenter
- let cancelError = FIRAuthErrorUtils.webContextCancelledError(withMessage: nil)
- authURLPresenter?.authURLPresentationResult = .failure(cancelError)
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- if case let .failure(error as NSError) = completion {
- XCTAssertEqual(error.code, AuthErrorCode.webContextCancelled.rawValue)
- getCredentialExpectation.fulfill()
- }
- } receiveValue: { authDataResult in
- XCTFail("💥 result unexpected")
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- func testGetCredentialWithUIDelegateNetworkRequestFailedWithClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let authURLPresenter = auth.authURLPresenter as? MockAuthURLPresenter
- authURLPresenter?.redirectURL = Self.redirectURLBaseErrorString + Self
- .networkRequestFailedErrorString
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- if case let .failure(error as NSError) = completion {
- XCTAssertEqual(error.code, AuthErrorCode.webNetworkRequestFailed.rawValue)
- getCredentialExpectation.fulfill()
- }
- } receiveValue: { authDataResult in
- XCTFail("💥 result unexpected")
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- func testGetCredentialWithUIDelegateInternalErrorWithClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let authURLPresenter = auth.authURLPresenter as? MockAuthURLPresenter
- authURLPresenter?.redirectURL = Self.redirectURLBaseErrorString + Self.internalErrorString
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- if case let .failure(error as NSError) = completion {
- XCTAssertEqual(error.code, AuthErrorCode.webInternalError.rawValue)
- getCredentialExpectation.fulfill()
- }
- } receiveValue: { authDataResult in
- XCTFail("💥 result unexpected")
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- func testGetCredentialWithUIDelegateInvalidClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let authURLPresenter = auth.authURLPresenter as? MockAuthURLPresenter
- authURLPresenter?.redirectURL = Self.redirectURLBaseErrorString
- authURLPresenter?.redirectURL.append(Self.invalidClientIDString)
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- if case let .failure(error as NSError) = completion {
- XCTAssertEqual(error.code, AuthErrorCode.invalidClientID.rawValue)
- getCredentialExpectation.fulfill()
- }
- } receiveValue: { authDataResult in
- XCTFail("💥 result unexpected")
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- func testGetCredentialWithUIDelegateUnknownErrorWithClientID() {
- // given
- FIRAuthBackend.setBackendImplementation(MockAuthBackend())
- var cancellables = Set<AnyCancellable>()
- let getCredentialExpectation = expectation(description: "Get credential")
- let uiDelegate = MockUIDelegate()
- let auth = MockAuth.auth()
- let authURLPresenter = auth.authURLPresenter as? MockAuthURLPresenter
- authURLPresenter?.redirectURL = Self.redirectURLBaseErrorString
- authURLPresenter?.redirectURL.append(Self.unknownErrorString)
- let provider = OAuthProvider(providerID: Self.providerID, auth: auth)
- provider.getCredentialWith(uiDelegate)
- .sink { completion in
- if case let .failure(error as NSError) = completion {
- XCTAssertEqual(
- error.code,
- AuthErrorCode.webSignInUserInteractionFailure.rawValue
- )
- getCredentialExpectation.fulfill()
- }
- } receiveValue: { authDataResult in
- XCTFail("💥 result unexpected")
- }
- .store(in: &cancellables)
- // then
- wait(for: [getCredentialExpectation], timeout: expectationTimeout)
- }
- }
|