GameCenterAuthProvider.swift 8.0 KB

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