SettingsViewController.m 16 KB

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