SettingsViewController.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "SettingsViewController.h"
  17. #import <objc/runtime.h>
  18. #import "FIRApp.h"
  19. #import "FIRAuth_Internal.h"
  20. #import "FIRAuthAPNSToken.h"
  21. #import "FIRAuthAPNSTokenManager.h"
  22. #import "FIRAuthAppCredential.h"
  23. #import "FIRAuthAppCredentialManager.h"
  24. #import "FIROptions.h"
  25. #import "FirebaseAuth.h"
  26. #import "StaticContentTableViewManager.h"
  27. #import "UIViewController+Alerts.h"
  28. // Declares a private method of FIRInstanceID to work around a bug.
  29. @interface FIRInstanceID : NSObject
  30. + (void)notifyTokenRefresh;
  31. @end
  32. /** @var kIdentityToolkitRequestClassName
  33. @brief The class name of Identity Toolkit requests.
  34. */
  35. static NSString *const kIdentityToolkitRequestClassName = @"FIRIdentityToolkitRequest";
  36. /** @var kSecureTokenRequestClassName
  37. @brief The class name of Secure Token Service requests.
  38. */
  39. static NSString *const kSecureTokenRequestClassName = @"FIRSecureTokenRequest";
  40. /** @var kIdentityToolkitSandboxHost
  41. @brief The host of Identity Toolkit sandbox server.
  42. */
  43. static NSString *const kIdentityToolkitSandboxHost = @"www-googleapis-staging.sandbox.google.com";
  44. /** @var kSecureTokenSandboxHost
  45. @brief The host of Secure Token Service sandbox server.
  46. */
  47. static NSString *const kSecureTokenSandboxHost = @"staging-securetoken.sandbox.googleapis.com";
  48. /** @var kGoogleServiceInfoPlists
  49. @brief a C-array of plist file base names of Google service info to initialize FIRApp.
  50. */
  51. static NSString *const kGoogleServiceInfoPlists[] = {
  52. @"GoogleService-Info",
  53. @"GoogleService-Info_multi"
  54. };
  55. /** @var gAPIEndpoints
  56. @brief List of API Hosts by request class name.
  57. */
  58. static NSDictionary<NSString *, NSArray<NSString *> *> *gAPIHosts;
  59. /** @var gFirebaseAppOptions
  60. @brief List of FIROptions.
  61. */
  62. static NSArray<FIROptions *> *gFirebaseAppOptions;
  63. /** @protocol RequestClass
  64. @brief A de-facto protocol followed by request class objects to access its API host.
  65. */
  66. @protocol RequestClass <NSObject>
  67. - (NSString *)host;
  68. - (void)setHost:(NSString *)host;
  69. @end
  70. /** @fn versionString
  71. @brief Constructs a version string to display.
  72. @param string The version in string form.
  73. @param number The version in number form.
  74. */
  75. static NSString *versionString(const unsigned char *string, const double number) {
  76. return [NSString stringWithFormat:@"\"%s\" (%g)", string, number];
  77. }
  78. /** @fn requestHost
  79. @brief Retrieves the API host for the request class.
  80. @param requestClassName The name of the request class.
  81. */
  82. static NSString *APIHost(NSString *requestClassName) {
  83. return [(id<RequestClass>)NSClassFromString(requestClassName) host];
  84. }
  85. /** @fn truncatedString
  86. @brief Truncates a string under a maximum length.
  87. @param string The original string to be truncated.
  88. @param length The maximum length of the truncated string.
  89. @return The truncated string, which is not longer than @c length.
  90. */
  91. static NSString *truncatedString(NSString *string, NSUInteger length) {
  92. if (string.length <= length) {
  93. return string;
  94. }
  95. NSUInteger half = (length - 3) / 2;
  96. return [NSString stringWithFormat:@"%@...%@",
  97. [string substringToIndex:half],
  98. [string substringFromIndex:string.length - half]];
  99. }
  100. /** @fn hexString
  101. @brief Converts a piece of data into a hexadecimal string.
  102. @param data The raw data.
  103. @return The hexadecimal string representation of the data.
  104. */
  105. static NSString *hexString(NSData *data) {
  106. NSMutableString *string = [NSMutableString stringWithCapacity:data.length * 2];
  107. const unsigned char *bytes = data.bytes;
  108. for (int idx = 0; idx < data.length; ++idx) {
  109. [string appendFormat:@"%02X", (int)bytes[idx]];
  110. }
  111. return string;
  112. }
  113. @implementation SettingsViewController
  114. - (void)viewDidLoad {
  115. [super viewDidLoad];
  116. [self setUpAPIHosts];
  117. [self setUpFirebaseAppOptions];
  118. [self loadTableView];
  119. }
  120. - (IBAction)done:(id)sender {
  121. [self dismissViewControllerAnimated:YES completion:nil];
  122. }
  123. - (void)setUpAPIHosts {
  124. if (gAPIHosts) {
  125. return;
  126. }
  127. gAPIHosts = @{
  128. kIdentityToolkitRequestClassName : @[
  129. APIHost(kIdentityToolkitRequestClassName),
  130. kIdentityToolkitSandboxHost,
  131. ],
  132. kSecureTokenRequestClassName : @[
  133. APIHost(kSecureTokenRequestClassName),
  134. kSecureTokenSandboxHost,
  135. ],
  136. };
  137. }
  138. - (void)setUpFirebaseAppOptions {
  139. if (gFirebaseAppOptions) {
  140. return;
  141. }
  142. int numberOfOptions = sizeof(kGoogleServiceInfoPlists) / sizeof(*kGoogleServiceInfoPlists);
  143. NSMutableArray *appOptions = [[NSMutableArray alloc] initWithCapacity:numberOfOptions];
  144. for (int i = 0; i < numberOfOptions; i++) {
  145. NSString *plistFileName = kGoogleServiceInfoPlists[i];
  146. NSString *plistFilePath = [[NSBundle mainBundle] pathForResource:plistFileName
  147. ofType:@"plist"];
  148. FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:plistFilePath];
  149. [appOptions addObject:options];
  150. }
  151. gFirebaseAppOptions = [appOptions copy];
  152. }
  153. - (void)loadTableView {
  154. __weak typeof(self) weakSelf = self;
  155. _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[
  156. [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[
  157. [StaticContentTableViewCell cellWithTitle:@"FirebaseAuth"
  158. value:versionString(
  159. FirebaseAuthVersionString, FirebaseAuthVersionNumber)],
  160. ]],
  161. [StaticContentTableViewSection sectionWithTitle:@"Client Identity" cells:@[
  162. [StaticContentTableViewCell cellWithTitle:@"Project"
  163. value:[self currentProjectID]
  164. action:^{
  165. [weakSelf toggleClientProject];
  166. }],
  167. ]],
  168. [StaticContentTableViewSection sectionWithTitle:@"API Hosts" cells:@[
  169. [StaticContentTableViewCell cellWithTitle:@"Identity Toolkit"
  170. value:APIHost(kIdentityToolkitRequestClassName)
  171. action:^{
  172. [weakSelf toggleAPIHostWithRequestClassName:kIdentityToolkitRequestClassName];
  173. }],
  174. [StaticContentTableViewCell cellWithTitle:@"Secure Token"
  175. value:APIHost(kSecureTokenRequestClassName)
  176. action:^{
  177. [weakSelf toggleAPIHostWithRequestClassName:kSecureTokenRequestClassName];
  178. }],
  179. ]],
  180. [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[
  181. [StaticContentTableViewCell cellWithTitle:@"APNs Token"
  182. value:[self APNSTokenString]
  183. action:^{
  184. [weakSelf clearAPNSToken];
  185. }],
  186. [StaticContentTableViewCell cellWithTitle:@"App Credential"
  187. value:[self appCredentialString]
  188. action:^{
  189. [weakSelf clearAppCredential];
  190. }],
  191. ]],
  192. ]];
  193. }
  194. /** @fn toggleAPIHostWithRequestClassName:
  195. @brief Toggles the host name of the server that handles RPCs.
  196. @param requestClassName The name of the RPC request class.
  197. */
  198. - (void)toggleAPIHostWithRequestClassName:(NSString *)requestClassName {
  199. NSString *currentHost = APIHost(requestClassName);
  200. NSArray<NSString *> *allHosts = gAPIHosts[requestClassName];
  201. NSString *newHost = allHosts[([allHosts indexOfObject:currentHost] + 1) % allHosts.count];
  202. [(id<RequestClass>)NSClassFromString(requestClassName) setHost:newHost];
  203. [self loadTableView];
  204. }
  205. /** @fn currentProjectID
  206. @brief Returns the the current Firebase project ID.
  207. */
  208. - (NSString *)currentProjectID {
  209. NSString *APIKey = [FIRApp defaultApp].options.APIKey;
  210. for (FIROptions *options in gFirebaseAppOptions) {
  211. if ([options.APIKey isEqualToString:APIKey]) {
  212. return options.projectID;
  213. }
  214. }
  215. return nil;
  216. }
  217. /** @fn toggleClientProject
  218. @brief Toggles the Firebase/Google project this client presents by recreating the default
  219. FIRApp instance with different options.
  220. */
  221. - (void)toggleClientProject {
  222. NSString *APIKey = [FIRApp defaultApp].options.APIKey;
  223. for (NSUInteger i = 0 ; i < gFirebaseAppOptions.count; i++) {
  224. FIROptions *options = gFirebaseAppOptions[i];
  225. if ([options.APIKey isEqualToString:APIKey]) {
  226. __weak typeof(self) weakSelf = self;
  227. [[FIRApp defaultApp] deleteApp:^(BOOL success) {
  228. if (success) {
  229. [FIRInstanceID notifyTokenRefresh]; // b/28967043
  230. dispatch_async(dispatch_get_main_queue(), ^() {
  231. FIROptions *options = gFirebaseAppOptions[(i + 1) % gFirebaseAppOptions.count];
  232. [FIRApp configureWithOptions:options];
  233. [weakSelf loadTableView];
  234. });
  235. }
  236. }];
  237. return;
  238. }
  239. }
  240. }
  241. /** @fn APNSTokenString
  242. @brief Returns a string representing APNS token.
  243. */
  244. - (NSString *)APNSTokenString {
  245. FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
  246. if (!token) {
  247. return @"";
  248. }
  249. return [NSString stringWithFormat:@"%@(%@)",
  250. truncatedString(hexString(token.data), 19),
  251. token.type == FIRAuthAPNSTokenTypeProd ? @"P" : @"S"];
  252. }
  253. /** @fn clearAPNSToken
  254. @brief Clears the saved app credential.
  255. */
  256. - (void)clearAPNSToken {
  257. FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
  258. if (!token) {
  259. return;
  260. }
  261. NSString *tokenType = token.type == FIRAuthAPNSTokenTypeProd ? @"Production" : @"Sandbox";
  262. NSString *message = [NSString stringWithFormat:@"token: %@\ntype: %@",
  263. hexString(token.data), tokenType];
  264. [self showMessagePromptWithTitle:@"Clear APNs Token?"
  265. message:message
  266. showCancelButton:YES
  267. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  268. if (userPressedOK) {
  269. [FIRAuth auth].tokenManager.token = nil;
  270. [self loadTableView];
  271. }
  272. }];
  273. }
  274. /** @fn appCredentialString
  275. @brief Returns a string representing app credential.
  276. */
  277. - (NSString *)appCredentialString {
  278. FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
  279. if (!credential) {
  280. return @"";
  281. }
  282. return [NSString stringWithFormat:@"%@/%@",
  283. truncatedString(credential.receipt, 13),
  284. truncatedString(credential.secret, 13)];
  285. }
  286. /** @fn clearAppCredential
  287. @brief Clears the saved app credential.
  288. */
  289. - (void)clearAppCredential {
  290. FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
  291. if (!credential) {
  292. return;
  293. }
  294. NSString *message = [NSString stringWithFormat:@"receipt: %@\nsecret: %@",
  295. credential.receipt, credential.secret];
  296. [self showMessagePromptWithTitle:@"Clear App Credential?"
  297. message:message
  298. showCancelButton:YES
  299. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  300. if (userPressedOK) {
  301. [[FIRAuth auth].appCredentialManager clearCredential];
  302. [self loadTableView];
  303. }
  304. }];
  305. }
  306. @end