GameCenterAuthProvider.swift 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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. /**
  28. @brief A concrete implementation of `AuthProvider` for Game Center Sign In. Not available on watchOS.
  29. */
  30. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  31. @objc(FIRGameCenterAuthProvider) open class GameCenterAuthProvider: NSObject {
  32. @objc public static let id = "gc.apple.com"
  33. /** @fn
  34. @brief Creates an `AuthCredential` for a Game Center sign in.
  35. */
  36. @objc open class func getCredential(completion: @escaping (AuthCredential?, Error?) -> Void) {
  37. /**
  38. Linking GameKit.framework without using it on macOS results in App Store rejection.
  39. Thus we don't link GameKit.framework to our SDK directly. `optionalLocalPlayer` is used for
  40. checking whether the APP that consuming our SDK has linked GameKit.framework. If not, a
  41. `GameKitNotLinkedError` will be raised.
  42. **/
  43. guard let _: AnyClass = NSClassFromString("GKLocalPlayer") else {
  44. completion(nil, AuthErrorUtils.gameKitNotLinkedError())
  45. return
  46. }
  47. let localPlayer = GKLocalPlayer.local
  48. guard localPlayer.isAuthenticated else {
  49. completion(nil, AuthErrorUtils.localPlayerNotAuthenticatedError())
  50. return
  51. }
  52. if #available(iOS 13.5, macOS 10.15.5, macCatalyst 13.5, tvOS 13.4.8, *) {
  53. localPlayer.fetchItems { publicKeyURL, signature, salt, timestamp, error in
  54. if let error = error {
  55. completion(nil, error)
  56. } else {
  57. let credential = GameCenterAuthCredential(withPlayerID: "",
  58. teamPlayerID: localPlayer.teamPlayerID,
  59. gamePlayerID: localPlayer.gamePlayerID,
  60. publicKeyURL: publicKeyURL,
  61. signature: signature,
  62. salt: salt,
  63. timestamp: timestamp,
  64. displayName: localPlayer.displayName)
  65. completion(credential, nil)
  66. }
  67. }
  68. } else {
  69. (GameCenterAuthProvider.self as WarningWorkaround.Type).pre135Credential(
  70. localPlayer: localPlayer, completion: completion
  71. )
  72. }
  73. }
  74. @available(iOS, deprecated: 13.0)
  75. @available(tvOS, deprecated: 13.0)
  76. @available(macOS, deprecated: 10.15.0)
  77. @available(macCatalyst, deprecated: 13.0)
  78. fileprivate class func pre135Credential(localPlayer: GKLocalPlayer,
  79. completion: @escaping (AuthCredential?, Error?)
  80. -> Void) {
  81. localPlayer
  82. .generateIdentityVerificationSignature { publicKeyURL, signature, salt, timestamp, error in
  83. if error != nil {
  84. completion(nil, error)
  85. } else {
  86. /**
  87. @c `localPlayer.alias` is actually the displayname needed, instead of
  88. `localPlayer.displayname`. For more information, check
  89. https://developer.apple.com/documentation/gamekit/gkplayer
  90. **/
  91. let displayName = localPlayer.alias
  92. let credential = GameCenterAuthCredential(withPlayerID: localPlayer.playerID,
  93. teamPlayerID: nil,
  94. gamePlayerID: nil,
  95. publicKeyURL: publicKeyURL,
  96. signature: signature,
  97. salt: salt,
  98. timestamp: timestamp,
  99. displayName: displayName)
  100. completion(credential, nil)
  101. }
  102. }
  103. }
  104. /** @fn
  105. @brief Creates an `AuthCredential` for a Game Center sign in.
  106. */
  107. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *)
  108. open class func getCredential() async throws -> AuthCredential {
  109. return try await withCheckedThrowingContinuation { continuation in
  110. getCredential { credential, error in
  111. if let credential = credential {
  112. continuation.resume(returning: credential)
  113. } else {
  114. continuation.resume(throwing: error!) // TODO: Change to ?? and generate unknown error
  115. }
  116. }
  117. }
  118. }
  119. @available(*, unavailable)
  120. @objc override public init() {
  121. fatalError("This class is not meant to be initialized.")
  122. }
  123. }
  124. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  125. @objc(FIRGameCenterAuthCredential) class GameCenterAuthCredential: AuthCredential,
  126. NSSecureCoding {
  127. let playerID: String
  128. let teamPlayerID: String?
  129. let gamePlayerID: String?
  130. let publicKeyURL: URL?
  131. let signature: Data?
  132. let salt: Data?
  133. let timestamp: UInt64
  134. let displayName: String
  135. /**
  136. @brief Designated initializer.
  137. @param playerID The ID of the Game Center local player.
  138. @param teamPlayerID The teamPlayerID of the Game Center local player.
  139. @param gamePlayerID The gamePlayerID of the Game Center local player.
  140. @param publicKeyURL The URL for the public encryption key.
  141. @param signature The verification signature generated.
  142. @param salt A random string used to compute the hash and keep it randomized.
  143. @param timestamp The date and time that the signature was created.
  144. @param displayName The display name of the Game Center player.
  145. */
  146. init(withPlayerID playerID: String, teamPlayerID: String?, gamePlayerID: String?,
  147. publicKeyURL: URL?, signature: Data?, salt: Data?,
  148. timestamp: UInt64, displayName: String) {
  149. self.playerID = playerID
  150. self.teamPlayerID = teamPlayerID
  151. self.gamePlayerID = gamePlayerID
  152. self.publicKeyURL = publicKeyURL
  153. self.signature = signature
  154. self.salt = salt
  155. self.timestamp = timestamp
  156. self.displayName = displayName
  157. super.init(provider: GameCenterAuthProvider.id)
  158. }
  159. // MARK: Secure Coding
  160. static var supportsSecureCoding = true
  161. func encode(with coder: NSCoder) {
  162. coder.encode(playerID, forKey: "playerID")
  163. coder.encode(teamPlayerID, forKey: "teamPlayerID")
  164. coder.encode(gamePlayerID, forKey: "gamePlayerID")
  165. coder.encode(publicKeyURL, forKey: "publicKeyURL")
  166. coder.encode(signature, forKey: "signature")
  167. coder.encode(salt, forKey: "salt")
  168. coder.encode(timestamp, forKey: "timestamp")
  169. coder.encode(displayName, forKey: "displayName")
  170. }
  171. required init?(coder: NSCoder) {
  172. guard let playerID = coder.decodeObject(of: NSString.self, forKey: "playerID") as? String,
  173. let teamPlayerID = coder.decodeObject(
  174. of: NSString.self,
  175. forKey: "teamPlayerID"
  176. ) as? String,
  177. let gamePlayerID = coder.decodeObject(
  178. of: NSString.self,
  179. forKey: "gamePlayerID"
  180. ) as? String,
  181. let timestamp = coder.decodeObject(of: NSNumber.self, forKey: "timestamp") as? UInt64,
  182. let displayName = coder.decodeObject(
  183. of: NSString.self,
  184. forKey: "displayName"
  185. ) as? String else {
  186. return nil
  187. }
  188. self.playerID = playerID
  189. self.teamPlayerID = teamPlayerID
  190. self.gamePlayerID = gamePlayerID
  191. self.timestamp = timestamp
  192. self.displayName = displayName
  193. publicKeyURL = coder.decodeObject(forKey: "publicKeyURL") as? URL
  194. signature = coder.decodeObject(of: NSData.self, forKey: "signature") as? Data
  195. salt = coder.decodeObject(of: NSData.self, forKey: "salt") as? Data
  196. super.init(provider: GameCenterAuthProvider.id)
  197. }
  198. }
  199. #endif