소스 검색

[FirebaseAuth] Add Sign-Out Support for BYO-CIAM in Regionalized GCIP (#15093)

Srushti Vaidya 8 달 전
부모
커밋
2b0406ce35

+ 19 - 11
FirebaseAuth/Sources/Swift/Auth/Auth.swift

@@ -1320,10 +1320,17 @@ extension Auth: AuthInterop {
   /// dictionary will contain more information about the error encountered.
   @objc(signOut:) open func signOut() throws {
     try kAuthGlobalWorkQueue.sync {
-      guard self._currentUser != nil else {
-        return
+      if self._currentUser != nil {
+        // Clear standard user session if one exists.
+        try self.updateCurrentUser(nil, byForce: false, savingToDisk: true)
+      }
+
+      // Clear R-GCIP token-only session.
+      self.rGCIPFirebaseTokenLock.withLock { token in
+        if token != nil {
+          token = nil
+        }
       }
-      return try self.updateCurrentUser(nil, byForce: false, savingToDisk: true)
     }
   }
 
@@ -2541,13 +2548,17 @@ public extension Auth {
   ///   call fails, or if the token response parsing fails.
   func exchangeToken(idToken: String, idpConfigId: String,
                      useStaging: Bool = false) async throws -> FirebaseToken {
-    // Ensure R-GCIP is configured with location and tenant ID
-    guard let _ = requestConfiguration.tenantConfig?.location,
-          let _ = requestConfiguration.tenantConfig?.tenantId
-    else {
+    /// Ensure R-GCIP is configured with location and tenant ID.
+    guard requestConfiguration.tenantConfig != nil else {
       /// This should never happen in production code, as it indicates a misconfiguration.
       fatalError("R-GCIP is not configured correctly.")
     }
+    /// This method should only be called on an R-GCIP instance
+    guard _currentUser == nil else {
+      fatalError(
+        "exchangeToken cannot be called on an Auth instance with an active standard user session."
+      )
+    }
     let request = ExchangeTokenRequest(
       idToken: idToken,
       idpConfigID: idpConfigId,
@@ -2560,11 +2571,8 @@ public extension Auth {
         token: response.firebaseToken,
         expirationDate: response.expirationDate
       )
-      // Lock and update the token, signing out any current user.
+      // Lock and update the R-GCIP token.
       rGCIPFirebaseTokenLock.withLock { token in
-        if self._currentUser != nil {
-          try? self.signOut()
-        }
         token = newToken
       }
       return newToken

+ 27 - 6
FirebaseAuth/Tests/SampleSwift/AuthenticationExample/AppManager.swift

@@ -25,25 +25,37 @@ class AppManager {
   private var otherApp: FirebaseApp
   var app: FirebaseApp
 
-  // Initialise Auth with TenantConfig
-  let tenantConfig = TenantConfig(tenantId: "Foo-e2e-tenant-007", location: "global")
+  // TenantConfig for regionalized Auth
+  private let tenantConfig = TenantConfig(tenantId: "Foo-e2e-tenant-007", location: "global")
+
+  // Cached Auth instances
+  private var defaultAppAuth: Auth
+  private var otherAppAuth: Auth
+  private var regionalAuthInstance: Auth
+
   func auth() -> Auth {
-    return Auth.auth(app: app, tenantConfig: tenantConfig)
+    /// Always return the specific regional instance
+    return regionalAuthInstance
   }
 
   private init() {
     defaultApp = FirebaseApp.app()!
-    app = FirebaseApp.app()!
+    defaultAppAuth = Auth.auth(app: defaultApp)
     guard let path = Bundle.main.path(forResource: "GoogleService-Info_multi", ofType: "plist"),
           let options = FirebaseOptions(contentsOfFile: path) else {
       fatalError("GoogleService-Info_multi.plist must be added to the project")
     }
-
-    FirebaseApp.configure(name: "OtherApp", options: options)
+    if FirebaseApp.app(name: "OtherApp") == nil {
+      FirebaseApp.configure(name: "OtherApp", options: options)
+    }
     guard let other = FirebaseApp.app(name: "OtherApp") else {
       fatalError("Failed to find OtherApp")
     }
     otherApp = other
+    otherAppAuth = Auth.auth(app: otherApp)
+    regionalAuthInstance = Auth.auth(app: otherApp, tenantConfig: tenantConfig)
+    print("AppManager init: regionalAuthInstance created: \(regionalAuthInstance)")
+    app = defaultApp
   }
 
   func toggle() {
@@ -53,4 +65,13 @@ class AppManager {
       app = defaultApp
     }
   }
+
+  /// Function to get the standard Auth instance based on the current 'app'
+  func standardAuth() -> Auth {
+    if app == defaultApp {
+      return defaultAppAuth
+    } else {
+      return otherAppAuth
+    }
+  }
 }

+ 6 - 0
FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Models/AuthMenu.swift

@@ -54,6 +54,7 @@ enum AuthMenu: String {
   case totpEnroll
   case multifactorUnenroll
   case exchangeToken
+  case exchangeTokenSignOut
 
   // More intuitively named getter for `rawValue`.
   var id: String { rawValue }
@@ -143,6 +144,8 @@ enum AuthMenu: String {
     // R-GCIP Exchange Token
     case .exchangeToken:
       return "Exchange Token"
+    case .exchangeTokenSignOut:
+      return "Sign Out from R-GCIP"
     }
   }
 
@@ -226,6 +229,8 @@ enum AuthMenu: String {
       self = .multifactorUnenroll
     case "Exchange Token":
       self = .exchangeToken
+    case "Sign Out from R-GCIP":
+      self = .exchangeTokenSignOut
     default:
       return nil
     }
@@ -364,6 +369,7 @@ class AuthMenuData: DataSourceProvidable {
     let header = "Exchange Token [Regionalized Auth]"
     let items: [Item] = [
       Item(title: AuthMenu.exchangeToken.name),
+      Item(title: AuthMenu.exchangeTokenSignOut.name),
     ]
     return Section(headerDescription: header, items: items)
   }

+ 32 - 0
FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift

@@ -194,6 +194,9 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {
 
     case .exchangeToken:
       callExchangeToken()
+
+    case .exchangeTokenSignOut:
+      callExchangeTokenSignOut()
     }
   }
 
@@ -1172,6 +1175,35 @@ extension AuthViewController: ASAuthorizationControllerDelegate,
     }
   }
 
+  private func callExchangeTokenSignOut() {
+    Task {
+      do {
+        try AppManager.shared.auth().signOut()
+        print("Sign out successful.")
+        await MainActor.run {
+          let alert = UIAlertController(
+            title: "Signed Out",
+            message: "The current R-GCIP session has been signed out.",
+            preferredStyle: .alert
+          )
+          alert.addAction(UIAlertAction(title: "OK", style: .default))
+          self.present(alert, animated: true)
+        }
+      } catch {
+        print("Failed to sign out: \(error)")
+        await MainActor.run {
+          let alert = UIAlertController(
+            title: "Sign Out Error",
+            message: error.localizedDescription,
+            preferredStyle: .alert
+          )
+          alert.addAction(UIAlertAction(title: "OK", style: .default))
+          self.present(alert, animated: true)
+        }
+      }
+    }
+  }
+
   // Helper function to truncate strings
   private func truncateString(_ string: String, maxLength: Int) -> String {
     if string.count > maxLength {