GameCenterAuthProvider.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #if !os(watchOS)
  15. import Foundation
  16. import GameKit
  17. // TODO: Delete this when minimum iOS version passes 13.5.
  18. /// WarningWorkaround is needed because playerID is deprecated in iOS 13.0 but still needed until
  19. /// 13.5 when the fetchItems API was introduced.
  20. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  21. private protocol WarningWorkaround {
  22. static func pre135Credential(localPlayer: GKLocalPlayer,
  23. completion: @escaping (AuthCredential?, Error?) -> Void)
  24. }
  25. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  26. extension GameCenterAuthProvider: WarningWorkaround {}
  27. /// A concrete implementation of `AuthProvider` for Game Center Sign In. Not available on watchOS.
  28. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  29. @objc(FIRGameCenterAuthProvider) open class GameCenterAuthProvider: NSObject {
  30. /// A string constant identifying the Game Center identity provider.
  31. @objc public static let id = "gc.apple.com"
  32. /// Creates an `AuthCredential` for a Game Center sign in.
  33. @objc open class func getCredential(completion: @escaping (AuthCredential?, Error?) -> Void) {
  34. /**
  35. Linking GameKit.framework without using it on macOS results in App Store rejection.
  36. Thus we don't link GameKit.framework to our SDK directly. `optionalLocalPlayer` is used for
  37. checking whether the APP that consuming our SDK has linked GameKit.framework. If not, a
  38. `GameKitNotLinkedError` will be raised.
  39. **/
  40. guard let _: AnyClass = NSClassFromString("GKLocalPlayer") else {
  41. completion(nil, AuthErrorUtils.gameKitNotLinkedError())
  42. return
  43. }
  44. let localPlayer = GKLocalPlayer.local
  45. guard localPlayer.isAuthenticated else {
  46. completion(nil, AuthErrorUtils.localPlayerNotAuthenticatedError())
  47. return
  48. }
  49. if #available(iOS 13.5, macOS 10.15.5, macCatalyst 13.5, tvOS 13.4.8, *) {
  50. localPlayer.fetchItems { publicKeyURL, signature, salt, timestamp, error in
  51. if let error = error {
  52. completion(nil, error)
  53. } else {
  54. let credential = GameCenterAuthCredential(withPlayerID: "",
  55. teamPlayerID: localPlayer.teamPlayerID,
  56. gamePlayerID: localPlayer.gamePlayerID,
  57. publicKeyURL: publicKeyURL,
  58. signature: signature,
  59. salt: salt,
  60. timestamp: timestamp,
  61. displayName: localPlayer.displayName)
  62. completion(credential, nil)
  63. }
  64. }
  65. } else {
  66. (GameCenterAuthProvider.self as WarningWorkaround.Type).pre135Credential(
  67. localPlayer: localPlayer, completion: completion
  68. )
  69. }
  70. }
  71. @available(iOS, deprecated: 13.0)
  72. @available(tvOS, deprecated: 13.0)
  73. @available(macOS, deprecated: 10.15.0)
  74. @available(macCatalyst, deprecated: 13.0)
  75. fileprivate class func pre135Credential(localPlayer: GKLocalPlayer,
  76. completion: @escaping (AuthCredential?, Error?)
  77. -> Void) {
  78. localPlayer
  79. .generateIdentityVerificationSignature { publicKeyURL, signature, salt, timestamp, error in
  80. if error != nil {
  81. completion(nil, error)
  82. } else {
  83. /**
  84. `localPlayer.alias` is actually the displayname needed, instead of
  85. `localPlayer.displayname`. For more information, check
  86. https://developer.apple.com/documentation/gamekit/gkplayer
  87. **/
  88. let displayName = localPlayer.alias
  89. let credential = GameCenterAuthCredential(withPlayerID: localPlayer.playerID,
  90. teamPlayerID: nil,
  91. gamePlayerID: nil,
  92. publicKeyURL: publicKeyURL,
  93. signature: signature,
  94. salt: salt,
  95. timestamp: timestamp,
  96. displayName: displayName)
  97. completion(credential, nil)
  98. }
  99. }
  100. }
  101. /// Creates an `AuthCredential` for a Game Center sign in.
  102. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  103. open class func getCredential() async throws -> AuthCredential {
  104. return try await withCheckedThrowingContinuation { continuation in
  105. getCredential { credential, error in
  106. if let credential = credential {
  107. continuation.resume(returning: credential)
  108. } else {
  109. continuation.resume(throwing: error!) // TODO: Change to ?? and generate unknown error
  110. }
  111. }
  112. }
  113. }
  114. @available(*, unavailable)
  115. @objc override public init() {
  116. fatalError("This class is not meant to be initialized.")
  117. }
  118. }
  119. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  120. @objc(FIRGameCenterAuthCredential)
  121. class GameCenterAuthCredential: AuthCredential, NSSecureCoding {
  122. let playerID: String
  123. let teamPlayerID: String?
  124. let gamePlayerID: String?
  125. let publicKeyURL: URL?
  126. let signature: Data?
  127. let salt: Data?
  128. let timestamp: UInt64
  129. let displayName: String
  130. /// - Parameter playerID: The ID of the Game Center local player.
  131. /// - Parameter teamPlayerID: The teamPlayerID of the Game Center local player.
  132. /// - Parameter gamePlayerID: The gamePlayerID of the Game Center local player.
  133. /// - Parameter publicKeyURL: The URL for the public encryption key.
  134. /// - Parameter signature: The verification signature generated.
  135. /// - Parameter salt: A random string used to compute the hash and keep it randomized.
  136. /// - Parameter timestamp: The date and time that the signature was created.
  137. /// - Parameter displayName: The display name of the Game Center player.
  138. init(withPlayerID playerID: String, teamPlayerID: String?, gamePlayerID: String?,
  139. publicKeyURL: URL?, signature: Data?, salt: Data?,
  140. timestamp: UInt64, displayName: String) {
  141. self.playerID = playerID
  142. self.teamPlayerID = teamPlayerID
  143. self.gamePlayerID = gamePlayerID
  144. self.publicKeyURL = publicKeyURL
  145. self.signature = signature
  146. self.salt = salt
  147. self.timestamp = timestamp
  148. self.displayName = displayName
  149. super.init(provider: GameCenterAuthProvider.id)
  150. }
  151. // MARK: Secure Coding
  152. static var supportsSecureCoding = true
  153. func encode(with coder: NSCoder) {
  154. coder.encode(playerID, forKey: "playerID")
  155. coder.encode(teamPlayerID, forKey: "teamPlayerID")
  156. coder.encode(gamePlayerID, forKey: "gamePlayerID")
  157. coder.encode(publicKeyURL, forKey: "publicKeyURL")
  158. coder.encode(signature, forKey: "signature")
  159. coder.encode(salt, forKey: "salt")
  160. coder.encode(timestamp, forKey: "timestamp")
  161. coder.encode(displayName, forKey: "displayName")
  162. }
  163. required init?(coder: NSCoder) {
  164. guard let playerID = coder.decodeObject(of: NSString.self, forKey: "playerID") as? String,
  165. let teamPlayerID = coder.decodeObject(
  166. of: NSString.self,
  167. forKey: "teamPlayerID"
  168. ) as? String,
  169. let gamePlayerID = coder.decodeObject(
  170. of: NSString.self,
  171. forKey: "gamePlayerID"
  172. ) as? String,
  173. let timestamp = coder.decodeObject(of: NSNumber.self, forKey: "timestamp") as? UInt64,
  174. let displayName = coder.decodeObject(
  175. of: NSString.self,
  176. forKey: "displayName"
  177. ) as? String else {
  178. return nil
  179. }
  180. self.playerID = playerID
  181. self.teamPlayerID = teamPlayerID
  182. self.gamePlayerID = gamePlayerID
  183. self.timestamp = timestamp
  184. self.displayName = displayName
  185. publicKeyURL = coder.decodeObject(forKey: "publicKeyURL") as? URL
  186. signature = coder.decodeObject(of: NSData.self, forKey: "signature") as? Data
  187. salt = coder.decodeObject(of: NSData.self, forKey: "salt") as? Data
  188. super.init(provider: GameCenterAuthProvider.id)
  189. }
  190. }
  191. #endif