SettingsViewController.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  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 "AppManager.h"
  19. #import "FIRApp.h"
  20. #import "FIRAuth_Internal.h"
  21. #import "FIRAuthAPNSToken.h"
  22. #import "FIRAuthAPNSTokenManager.h"
  23. #import "FIRAuthAppCredential.h"
  24. #import "FIRAuthAppCredentialManager.h"
  25. #import "FIROptions.h"
  26. #import "FirebaseAuth.h"
  27. #import "StaticContentTableViewManager.h"
  28. #import "UIViewController+Alerts.h"
  29. /** @var kIdentityToolkitRequestClassName
  30. @brief The class name of Identity Toolkit requests.
  31. */
  32. static NSString *const kIdentityToolkitRequestClassName = @"FIRIdentityToolkitRequest";
  33. /** @var kSecureTokenRequestClassName
  34. @brief The class name of Secure Token Service requests.
  35. */
  36. static NSString *const kSecureTokenRequestClassName = @"FIRSecureTokenRequest";
  37. /** @var kIdentityToolkitSandboxHost
  38. @brief The host of Identity Toolkit sandbox server.
  39. */
  40. static NSString *const kIdentityToolkitSandboxHost = @"www-googleapis-staging.sandbox.google.com";
  41. /** @var kSecureTokenSandboxHost
  42. @brief The host of Secure Token Service sandbox server.
  43. */
  44. static NSString *const kSecureTokenSandboxHost = @"staging-securetoken.sandbox.googleapis.com";
  45. /** @var kGoogleServiceInfoPlists
  46. @brief a C-array of plist file base names of Google service info to initialize FIRApp.
  47. */
  48. static NSString *const kGoogleServiceInfoPlists[] = {
  49. @"GoogleService-Info",
  50. @"GoogleService-Info_multi"
  51. };
  52. /** @var gAPIEndpoints
  53. @brief List of API Hosts by request class name.
  54. */
  55. static NSDictionary<NSString *, NSArray<NSString *> *> *gAPIHosts;
  56. /** @var gFirebaseAppOptions
  57. @brief List of FIROptions.
  58. */
  59. static NSArray<FIROptions *> *gFirebaseAppOptions;
  60. /** @protocol RequestClass
  61. @brief A de-facto protocol followed by request class objects to access its API host.
  62. */
  63. @protocol RequestClass <NSObject>
  64. - (NSString *)host;
  65. - (void)setHost:(NSString *)host;
  66. @end
  67. /** @fn versionString
  68. @brief Constructs a version string to display.
  69. @param string The version in string form.
  70. @param number The version in number form.
  71. */
  72. static NSString *versionString(const unsigned char *string, const double number) {
  73. return [NSString stringWithFormat:@"\"%s\" (%g)", string, number];
  74. }
  75. /** @fn requestHost
  76. @brief Retrieves the API host for the request class.
  77. @param requestClassName The name of the request class.
  78. */
  79. static NSString *APIHost(NSString *requestClassName) {
  80. return [(id<RequestClass>)NSClassFromString(requestClassName) host];
  81. }
  82. /** @fn truncatedString
  83. @brief Truncates a string under a maximum length.
  84. @param string The original string to be truncated.
  85. @param length The maximum length of the truncated string.
  86. @return The truncated string, which is not longer than @c length.
  87. */
  88. static NSString *truncatedString(NSString *string, NSUInteger length) {
  89. if (string.length <= length) {
  90. return string;
  91. }
  92. NSUInteger half = (length - 3) / 2;
  93. return [NSString stringWithFormat:@"%@...%@",
  94. [string substringToIndex:half],
  95. [string substringFromIndex:string.length - half]];
  96. }
  97. @implementation SettingsViewController
  98. - (void)viewDidLoad {
  99. [super viewDidLoad];
  100. [self setUpAPIHosts];
  101. [self setUpFirebaseAppOptions];
  102. [self loadTableView];
  103. }
  104. - (IBAction)done:(id)sender {
  105. [self dismissViewControllerAnimated:YES completion:nil];
  106. }
  107. - (void)setUpAPIHosts {
  108. if (gAPIHosts) {
  109. return;
  110. }
  111. gAPIHosts = @{
  112. kIdentityToolkitRequestClassName : @[
  113. APIHost(kIdentityToolkitRequestClassName),
  114. kIdentityToolkitSandboxHost,
  115. ],
  116. kSecureTokenRequestClassName : @[
  117. APIHost(kSecureTokenRequestClassName),
  118. kSecureTokenSandboxHost,
  119. ],
  120. };
  121. }
  122. - (void)setUpFirebaseAppOptions {
  123. if (gFirebaseAppOptions) {
  124. return;
  125. }
  126. int numberOfOptions = sizeof(kGoogleServiceInfoPlists) / sizeof(*kGoogleServiceInfoPlists);
  127. NSMutableArray *appOptions = [[NSMutableArray alloc] initWithCapacity:numberOfOptions];
  128. for (int i = 0; i < numberOfOptions; i++) {
  129. NSString *plistFileName = kGoogleServiceInfoPlists[i];
  130. NSString *plistFilePath = [[NSBundle mainBundle] pathForResource:plistFileName
  131. ofType:@"plist"];
  132. FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:plistFilePath];
  133. [appOptions addObject:options];
  134. }
  135. gFirebaseAppOptions = [appOptions copy];
  136. }
  137. - (void)loadTableView {
  138. __weak typeof(self) weakSelf = self;
  139. _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[
  140. [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[
  141. [StaticContentTableViewCell cellWithTitle:@"FirebaseAuth"
  142. value:versionString(
  143. FirebaseAuthVersionString, FirebaseAuthVersionNumber)],
  144. ]],
  145. [StaticContentTableViewSection sectionWithTitle:@"API Hosts" cells:@[
  146. [StaticContentTableViewCell cellWithTitle:@"Identity Toolkit"
  147. value:APIHost(kIdentityToolkitRequestClassName)
  148. action:^{
  149. [weakSelf toggleAPIHostWithRequestClassName:kIdentityToolkitRequestClassName];
  150. }],
  151. [StaticContentTableViewCell cellWithTitle:@"Secure Token"
  152. value:APIHost(kSecureTokenRequestClassName)
  153. action:^{
  154. [weakSelf toggleAPIHostWithRequestClassName:kSecureTokenRequestClassName];
  155. }],
  156. ]],
  157. [StaticContentTableViewSection sectionWithTitle:@"Firebase Apps" cells:@[
  158. [StaticContentTableViewCell cellWithTitle:@"Active App"
  159. value:[self activeAppDescription]
  160. action:^{
  161. [weakSelf toggleActiveApp];
  162. }],
  163. [StaticContentTableViewCell cellWithTitle:@"Default App"
  164. value:[self projectIDForAppAtIndex:0]
  165. action:^{
  166. [weakSelf toggleProjectForAppAtIndex:0];
  167. }],
  168. [StaticContentTableViewCell cellWithTitle:@"Other App"
  169. value:[self projectIDForAppAtIndex:1]
  170. action:^{
  171. [weakSelf toggleProjectForAppAtIndex:1];
  172. }],
  173. ]],
  174. [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[
  175. [StaticContentTableViewCell cellWithTitle:@"APNs Token"
  176. value:[self APNSTokenString]
  177. action:^{
  178. [weakSelf clearAPNSToken];
  179. }],
  180. [StaticContentTableViewCell cellWithTitle:@"App Credential"
  181. value:[self appCredentialString]
  182. action:^{
  183. [weakSelf clearAppCredential];
  184. }],
  185. ]],
  186. [StaticContentTableViewSection sectionWithTitle:@"Language" cells:@[
  187. [StaticContentTableViewCell cellWithTitle:@"Auth Language"
  188. value:[AppManager auth].languageCode ?: @"[none]"
  189. action:^{
  190. [weakSelf showLanguageInput];
  191. }],
  192. [StaticContentTableViewCell cellWithTitle:@"Use App language" action:^{
  193. [[AppManager auth] useAppLanguage];
  194. [weakSelf loadTableView];
  195. }],
  196. ]],
  197. ]];
  198. }
  199. /** @fn toggleAPIHostWithRequestClassName:
  200. @brief Toggles the host name of the server that handles RPCs.
  201. @param requestClassName The name of the RPC request class.
  202. */
  203. - (void)toggleAPIHostWithRequestClassName:(NSString *)requestClassName {
  204. NSString *currentHost = APIHost(requestClassName);
  205. NSArray<NSString *> *allHosts = gAPIHosts[requestClassName];
  206. NSString *newHost = allHosts[([allHosts indexOfObject:currentHost] + 1) % allHosts.count];
  207. [(id<RequestClass>)NSClassFromString(requestClassName) setHost:newHost];
  208. [self loadTableView];
  209. }
  210. /** @fn activeAppDescription
  211. @brief Returns the description for the currently active Firebase app.
  212. */
  213. - (NSString *)activeAppDescription {
  214. return [AppManager sharedInstance].active == 0 ? @"[Default]" : @"[Other]";
  215. }
  216. /** @fn toggleActiveApp
  217. @brief Toggles the active Firebase app for the rest of the application.
  218. */
  219. - (void)toggleActiveApp {
  220. AppManager *apps = [AppManager sharedInstance];
  221. // This changes the FIRAuth instance returned from `[AppManager auth]` to be one that is
  222. // associated with a different `FIRApp` instance. The sample app uses `[AppManager auth]`
  223. // instead of `[FIRAuth auth]` almost everywhere. Thus, this statement switches between default
  224. // and non-default `FIRApp` instances for the sample app to test against.
  225. apps.active = (apps.active + 1) % apps.count;
  226. [self loadTableView];
  227. }
  228. /** @fn projectIDForAppAtIndex:
  229. @brief Returns the Firebase project ID for the Firebase app at the given index.
  230. @param index The index for the app in the app manager.
  231. @return The ID of the project.
  232. */
  233. - (NSString *)projectIDForAppAtIndex:(int)index {
  234. NSString *APIKey = [[AppManager sharedInstance] appAtIndex:index].options.APIKey;
  235. for (FIROptions *options in gFirebaseAppOptions) {
  236. if ([options.APIKey isEqualToString:APIKey]) {
  237. return options.projectID;
  238. }
  239. }
  240. return @"[none]";
  241. }
  242. /** @fn toggleProjectForAppAtIndex:
  243. @brief Toggles the Firebase project for the Firebase app at the given index by recreating the
  244. FIRApp instance with different options.
  245. @param index The index for the app to be recreated in the app manager.
  246. */
  247. - (void)toggleProjectForAppAtIndex:(int)index {
  248. NSString *APIKey = [[AppManager sharedInstance] appAtIndex:index].options.APIKey;
  249. int optionIndex;
  250. for (optionIndex = 0; optionIndex < gFirebaseAppOptions.count; optionIndex++) {
  251. FIROptions *options = gFirebaseAppOptions[optionIndex];
  252. if ([options.APIKey isEqualToString:APIKey]) {
  253. break;
  254. }
  255. }
  256. // For non-default apps, `nil` is considered the next option after the last options in the array.
  257. int useNil = index > 0;
  258. optionIndex = (optionIndex + 1 + useNil) % (gFirebaseAppOptions.count + useNil) - useNil;
  259. FIROptions *options = optionIndex >= 0 ? gFirebaseAppOptions[optionIndex] : nil;
  260. __weak typeof(self) weakSelf = self;
  261. [[AppManager sharedInstance] recreateAppAtIndex:index withOptions:options completion:^() {
  262. dispatch_async(dispatch_get_main_queue(), ^() {
  263. [weakSelf loadTableView];
  264. });
  265. }];
  266. }
  267. /** @fn APNSTokenString
  268. @brief Returns a string representing APNS token.
  269. */
  270. - (NSString *)APNSTokenString {
  271. FIRAuthAPNSToken *token = [AppManager auth].tokenManager.token;
  272. if (!token) {
  273. return @"";
  274. }
  275. return [NSString stringWithFormat:@"%@(%@)",
  276. truncatedString(token.string, 19),
  277. token.type == FIRAuthAPNSTokenTypeProd ? @"P" : @"S"];
  278. }
  279. /** @fn clearAPNSToken
  280. @brief Clears the saved app credential.
  281. */
  282. - (void)clearAPNSToken {
  283. FIRAuthAPNSToken *token = [AppManager auth].tokenManager.token;
  284. if (!token) {
  285. return;
  286. }
  287. NSString *tokenType = token.type == FIRAuthAPNSTokenTypeProd ? @"Production" : @"Sandbox";
  288. NSString *message = [NSString stringWithFormat:@"token: %@\ntype: %@",
  289. token.string, tokenType];
  290. [self showMessagePromptWithTitle:@"Clear APNs Token?"
  291. message:message
  292. showCancelButton:YES
  293. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  294. if (userPressedOK) {
  295. [AppManager auth].tokenManager.token = nil;
  296. [self loadTableView];
  297. }
  298. }];
  299. }
  300. /** @fn appCredentialString
  301. @brief Returns a string representing app credential.
  302. */
  303. - (NSString *)appCredentialString {
  304. FIRAuthAppCredential *credential = [AppManager auth].appCredentialManager.credential;
  305. if (!credential) {
  306. return @"";
  307. }
  308. return [NSString stringWithFormat:@"%@/%@",
  309. truncatedString(credential.receipt, 13),
  310. truncatedString(credential.secret, 13)];
  311. }
  312. /** @fn clearAppCredential
  313. @brief Clears the saved app credential.
  314. */
  315. - (void)clearAppCredential {
  316. FIRAuthAppCredential *credential = [AppManager auth].appCredentialManager.credential;
  317. if (!credential) {
  318. return;
  319. }
  320. NSString *message = [NSString stringWithFormat:@"receipt: %@\nsecret: %@",
  321. credential.receipt, credential.secret];
  322. [self showMessagePromptWithTitle:@"Clear App Credential?"
  323. message:message
  324. showCancelButton:YES
  325. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  326. if (userPressedOK) {
  327. [[AppManager auth].appCredentialManager clearCredential];
  328. [self loadTableView];
  329. }
  330. }];
  331. }
  332. /** @fn showLanguageInput
  333. @brief Show language code input field.
  334. */
  335. - (void)showLanguageInput {
  336. [self showTextInputPromptWithMessage:@"Enter Language Code For Auth:"
  337. completionBlock:^(BOOL userPressedOK, NSString *_Nullable languageCode) {
  338. if (!userPressedOK) {
  339. return;
  340. }
  341. [AppManager auth].languageCode = languageCode.length ? languageCode : nil;
  342. [self loadTableView];
  343. }];
  344. }
  345. @end