SettingsViewController.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /*
  2. * Copyright 2019 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 <FirebaseCore/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 <FirebaseCore/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 = @"staging-www.sandbox.googleapis.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 kSharedKeychainAccessGroup
  53. @brief The shared keychain access group for testing.
  54. */
  55. static NSString *const kSharedKeychainAccessGroup = @"com.google.firebase.auth.keychainGroup1";
  56. /** @var gAPIEndpoints
  57. @brief List of API Hosts by request class name.
  58. */
  59. static NSDictionary<NSString *, NSArray<NSString *> *> *gAPIHosts;
  60. /** @var gFirebaseAppOptions
  61. @brief List of FIROptions.
  62. */
  63. static NSArray<FIROptions *> *gFirebaseAppOptions;
  64. /** @protocol RequestClass
  65. @brief A de-facto protocol followed by request class objects to access its API host.
  66. */
  67. @protocol RequestClass <NSObject>
  68. - (NSString *)host;
  69. - (void)setHost:(NSString *)host;
  70. @end
  71. /** @fn versionString
  72. @brief Constructs a version string to display.
  73. @param string The version in string form.
  74. @param number The version in number form.
  75. */
  76. static NSString *versionString(const char *string, const double number) {
  77. return [NSString stringWithFormat:@"\"%s\" (%g)", string, number];
  78. }
  79. /** @fn requestHost
  80. @brief Retrieves the API host for the request class.
  81. @param requestClassName The name of the request class.
  82. */
  83. static NSString *APIHost(NSString *requestClassName) {
  84. return [(id<RequestClass>)NSClassFromString(requestClassName) host];
  85. }
  86. /** @fn truncatedString
  87. @brief Truncates a string under a maximum length.
  88. @param string The original string to be truncated.
  89. @param length The maximum length of the truncated string.
  90. @return The truncated string, which is not longer than @c length.
  91. */
  92. static NSString *truncatedString(NSString *string, NSUInteger length) {
  93. if (string.length <= length) {
  94. return string;
  95. }
  96. NSUInteger half = (length - 3) / 2;
  97. return [NSString stringWithFormat:@"%@...%@",
  98. [string substringToIndex:half],
  99. [string substringFromIndex:string.length - half]];
  100. }
  101. @implementation SettingsViewController
  102. - (void)viewDidLoad {
  103. [super viewDidLoad];
  104. [self setUpAPIHosts];
  105. [self setUpFirebaseAppOptions];
  106. [self loadTableView];
  107. }
  108. - (IBAction)done:(id)sender {
  109. [self dismissViewControllerAnimated:YES completion:nil];
  110. }
  111. - (void)setUpAPIHosts {
  112. if (gAPIHosts) {
  113. return;
  114. }
  115. gAPIHosts = @{
  116. kIdentityToolkitRequestClassName : @[
  117. APIHost(kIdentityToolkitRequestClassName),
  118. kIdentityToolkitSandboxHost,
  119. ],
  120. kSecureTokenRequestClassName : @[
  121. APIHost(kSecureTokenRequestClassName),
  122. kSecureTokenSandboxHost,
  123. ],
  124. };
  125. }
  126. - (void)setUpFirebaseAppOptions {
  127. if (gFirebaseAppOptions) {
  128. return;
  129. }
  130. int numberOfOptions = sizeof(kGoogleServiceInfoPlists) / sizeof(*kGoogleServiceInfoPlists);
  131. NSMutableArray *appOptions = [[NSMutableArray alloc] initWithCapacity:numberOfOptions];
  132. for (int i = 0; i < numberOfOptions; i++) {
  133. NSString *plistFileName = kGoogleServiceInfoPlists[i];
  134. NSString *plistFilePath = [[NSBundle mainBundle] pathForResource:plistFileName
  135. ofType:@"plist"];
  136. FIROptions *options = [[FIROptions alloc] initWithContentsOfFile:plistFilePath];
  137. [appOptions addObject:options];
  138. }
  139. gFirebaseAppOptions = [appOptions copy];
  140. }
  141. - (void)loadTableView {
  142. NSString *appIdentifierPrefix = NSBundle.mainBundle.infoDictionary[@"AppIdentifierPrefix"];
  143. NSString *fullKeychainAccessGroup = [appIdentifierPrefix stringByAppendingString:kSharedKeychainAccessGroup];
  144. __weak typeof(self) weakSelf = self;
  145. _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[
  146. [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[
  147. [StaticContentTableViewCell cellWithTitle:@"FirebaseAuth"
  148. value:versionString(
  149. FirebaseAuthVersionStr, FirebaseAuthVersionNum)],
  150. ]],
  151. [StaticContentTableViewSection sectionWithTitle:@"API Hosts" cells:@[
  152. [StaticContentTableViewCell cellWithTitle:@"Identity Toolkit"
  153. value:APIHost(kIdentityToolkitRequestClassName)
  154. action:^{
  155. [weakSelf toggleAPIHostWithRequestClassName:kIdentityToolkitRequestClassName];
  156. }],
  157. [StaticContentTableViewCell cellWithTitle:@"Secure Token"
  158. value:APIHost(kSecureTokenRequestClassName)
  159. action:^{
  160. [weakSelf toggleAPIHostWithRequestClassName:kSecureTokenRequestClassName];
  161. }],
  162. ]],
  163. [StaticContentTableViewSection sectionWithTitle:@"Firebase Apps" cells:@[
  164. [StaticContentTableViewCell cellWithTitle:@"Active App"
  165. value:[self activeAppDescription]
  166. action:^{
  167. [weakSelf toggleActiveApp];
  168. }],
  169. [StaticContentTableViewCell cellWithTitle:@"Default App"
  170. value:[self projectIDForAppAtIndex:0]
  171. action:^{
  172. [weakSelf toggleProjectForAppAtIndex:0];
  173. }],
  174. [StaticContentTableViewCell cellWithTitle:@"Other App"
  175. value:[self projectIDForAppAtIndex:1]
  176. action:^{
  177. [weakSelf toggleProjectForAppAtIndex:1];
  178. }],
  179. ]],
  180. [StaticContentTableViewSection sectionWithTitle:@"Keychain Access Groups" cells:@[
  181. [StaticContentTableViewCell cellWithTitle:@"Current Access Group"
  182. value:[AppManager auth].userAccessGroup ? [AppManager auth].userAccessGroup : @"[none]"
  183. ],
  184. [StaticContentTableViewCell cellWithTitle:@"Default Group"
  185. value:@"[none]"
  186. action:^{
  187. [[AppManager auth] useUserAccessGroup:nil error:nil];
  188. [self loadTableView];
  189. }],
  190. [StaticContentTableViewCell cellWithTitle:@"Shared Group"
  191. value:fullKeychainAccessGroup
  192. action:^{
  193. [[AppManager auth] useUserAccessGroup:fullKeychainAccessGroup error:nil];
  194. [self loadTableView];
  195. }],
  196. ]],
  197. [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[
  198. [StaticContentTableViewCell cellWithTitle:@"APNs Token"
  199. value:[self APNSTokenString]
  200. action:^{
  201. [weakSelf clearAPNSToken];
  202. }],
  203. [StaticContentTableViewCell cellWithTitle:@"App Credential"
  204. value:[self appCredentialString]
  205. action:^{
  206. [weakSelf clearAppCredential];
  207. }],
  208. ]],
  209. [StaticContentTableViewSection sectionWithTitle:@"Language" cells:@[
  210. [StaticContentTableViewCell cellWithTitle:@"Auth Language"
  211. value:[AppManager auth].languageCode ?: @"[none]"
  212. action:^{
  213. [weakSelf showLanguageInput];
  214. }],
  215. [StaticContentTableViewCell cellWithTitle:@"Use App language" action:^{
  216. [[AppManager auth] useAppLanguage];
  217. [weakSelf loadTableView];
  218. }],
  219. ]],
  220. [StaticContentTableViewSection sectionWithTitle:@"Auth Settings" cells:@[
  221. [StaticContentTableViewCell cellWithTitle:@"Disable App Verification (Phone)"
  222. value:[AppManager auth].settings.
  223. appVerificationDisabledForTesting ? @"Yes" : @"No"
  224. action:^{
  225. [weakSelf toggleDisableAppVerification];
  226. [weakSelf loadTableView];
  227. }],
  228. ]],
  229. ]];
  230. }
  231. /** @fn toggleDisableAppVerification
  232. @brief Toggles the appVerificationDisabledForTesting flag on the current Auth instance.
  233. */
  234. - (void)toggleDisableAppVerification {
  235. [AppManager auth].settings.appVerificationDisabledForTesting =
  236. ![AppManager auth].settings.appVerificationDisabledForTesting;
  237. }
  238. /** @fn toggleAPIHostWithRequestClassName:
  239. @brief Toggles the host name of the server that handles RPCs.
  240. @param requestClassName The name of the RPC request class.
  241. */
  242. - (void)toggleAPIHostWithRequestClassName:(NSString *)requestClassName {
  243. NSString *currentHost = APIHost(requestClassName);
  244. NSArray<NSString *> *allHosts = gAPIHosts[requestClassName];
  245. NSString *newHost = allHosts[([allHosts indexOfObject:currentHost] + 1) % allHosts.count];
  246. [(id<RequestClass>)NSClassFromString(requestClassName) setHost:newHost];
  247. [self loadTableView];
  248. }
  249. /** @fn activeAppDescription
  250. @brief Returns the description for the currently active Firebase app.
  251. */
  252. - (NSString *)activeAppDescription {
  253. return [AppManager sharedInstance].active == 0 ? @"[Default]" : @"[Other]";
  254. }
  255. /** @fn toggleActiveApp
  256. @brief Toggles the active Firebase app for the rest of the application.
  257. */
  258. - (void)toggleActiveApp {
  259. AppManager *apps = [AppManager sharedInstance];
  260. // This changes the FIRAuth instance returned from `[AppManager auth]` to be one that is
  261. // associated with a different `FIRApp` instance. The sample app uses `[AppManager auth]`
  262. // instead of `[FIRAuth auth]` almost everywhere. Thus, this statement switches between default
  263. // and non-default `FIRApp` instances for the sample app to test against.
  264. apps.active = (apps.active + 1) % apps.count;
  265. [self loadTableView];
  266. }
  267. /** @fn projectIDForAppAtIndex:
  268. @brief Returns the Firebase project ID for the Firebase app at the given index.
  269. @param index The index for the app in the app manager.
  270. @return The ID of the project.
  271. */
  272. - (NSString *)projectIDForAppAtIndex:(int)index {
  273. NSString *APIKey = [[AppManager sharedInstance] appAtIndex:index].options.APIKey;
  274. for (FIROptions *options in gFirebaseAppOptions) {
  275. if ([options.APIKey isEqualToString:APIKey]) {
  276. return options.projectID;
  277. }
  278. }
  279. return @"[none]";
  280. }
  281. /** @fn projectIDForAppAtIndex:
  282. @brief Returns the Firebase project ID for the Firebase app at the given index.
  283. @param index The index for the app in the app manager.
  284. @return The ID of the project.
  285. */
  286. - (NSString *)keychainAccessGroupAtIndex:(int)index {
  287. NSArray *array = @[@"123", @"456"];
  288. return array[index];
  289. }
  290. /** @fn toggleProjectForAppAtIndex:
  291. @brief Toggles the Firebase project for the Firebase app at the given index by recreating the
  292. FIRApp instance with different options.
  293. @param index The index for the app to be recreated in the app manager.
  294. */
  295. - (void)toggleProjectForAppAtIndex:(int)index {
  296. NSString *APIKey = [[AppManager sharedInstance] appAtIndex:index].options.APIKey;
  297. int optionIndex;
  298. for (optionIndex = 0; optionIndex < gFirebaseAppOptions.count; optionIndex++) {
  299. FIROptions *options = gFirebaseAppOptions[optionIndex];
  300. if ([options.APIKey isEqualToString:APIKey]) {
  301. break;
  302. }
  303. }
  304. FIROptions *options;
  305. if (index == 0) {
  306. // For default apps, the next options cannot be `nil`.
  307. optionIndex = (optionIndex + 1) % gFirebaseAppOptions.count;
  308. options = gFirebaseAppOptions[optionIndex];
  309. } else {
  310. // For non-default apps, `nil` is considered the next options after the last options in the array.
  311. optionIndex = (optionIndex + 1) % (gFirebaseAppOptions.count + 1);
  312. if (optionIndex != gFirebaseAppOptions.count) {
  313. options = gFirebaseAppOptions[optionIndex];
  314. }
  315. }
  316. __weak typeof(self) weakSelf = self;
  317. [[AppManager sharedInstance] recreateAppAtIndex:index withOptions:options completion:^() {
  318. dispatch_async(dispatch_get_main_queue(), ^() {
  319. [weakSelf loadTableView];
  320. });
  321. }];
  322. }
  323. /** @fn APNSTokenString
  324. @brief Returns a string representing APNS token.
  325. */
  326. - (NSString *)APNSTokenString {
  327. FIRAuthAPNSToken *token = [AppManager auth].tokenManager.token;
  328. if (!token) {
  329. return @"";
  330. }
  331. return [NSString stringWithFormat:@"%@(%@)",
  332. truncatedString(token.string, 19),
  333. token.type == FIRAuthAPNSTokenTypeProd ? @"P" : @"S"];
  334. }
  335. /** @fn clearAPNSToken
  336. @brief Clears the saved app credential.
  337. */
  338. - (void)clearAPNSToken {
  339. FIRAuthAPNSToken *token = [AppManager auth].tokenManager.token;
  340. if (!token) {
  341. return;
  342. }
  343. NSString *tokenType = token.type == FIRAuthAPNSTokenTypeProd ? @"Production" : @"Sandbox";
  344. NSString *message = [NSString stringWithFormat:@"token: %@\ntype: %@",
  345. token.string, tokenType];
  346. [self showMessagePromptWithTitle:@"Clear APNs Token?"
  347. message:message
  348. showCancelButton:YES
  349. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  350. if (userPressedOK) {
  351. [AppManager auth].tokenManager.token = nil;
  352. [self loadTableView];
  353. }
  354. }];
  355. }
  356. /** @fn appCredentialString
  357. @brief Returns a string representing app credential.
  358. */
  359. - (NSString *)appCredentialString {
  360. FIRAuthAppCredential *credential = [AppManager auth].appCredentialManager.credential;
  361. if (!credential) {
  362. return @"";
  363. }
  364. return [NSString stringWithFormat:@"%@/%@",
  365. truncatedString(credential.receipt, 13),
  366. truncatedString(credential.secret, 13)];
  367. }
  368. /** @fn clearAppCredential
  369. @brief Clears the saved app credential.
  370. */
  371. - (void)clearAppCredential {
  372. FIRAuthAppCredential *credential = [AppManager auth].appCredentialManager.credential;
  373. if (!credential) {
  374. return;
  375. }
  376. NSString *message = [NSString stringWithFormat:@"receipt: %@\nsecret: %@",
  377. credential.receipt, credential.secret];
  378. [self showMessagePromptWithTitle:@"Clear App Credential?"
  379. message:message
  380. showCancelButton:YES
  381. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  382. if (userPressedOK) {
  383. [[AppManager auth].appCredentialManager clearCredential];
  384. [self loadTableView];
  385. }
  386. }];
  387. }
  388. /** @fn showLanguageInput
  389. @brief Show language code input field.
  390. */
  391. - (void)showLanguageInput {
  392. [self showTextInputPromptWithMessage:@"Enter Language Code For Auth:"
  393. completionBlock:^(BOOL userPressedOK, NSString *_Nullable languageCode) {
  394. if (!userPressedOK) {
  395. return;
  396. }
  397. [AppManager auth].languageCode = languageCode.length ? languageCode : nil;
  398. [self loadTableView];
  399. }];
  400. }
  401. @end