SettingsViewController.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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. /** @category FIROptions(ProjectID)
  71. @brief A category to FIROption to add the project ID property.
  72. */
  73. @interface FIROptions (ProjectID)
  74. /** @property projectID
  75. @brief The Firebase project ID.
  76. */
  77. @property(nonatomic, copy) NSString *projectID;
  78. @end
  79. @implementation FIROptions (ProjectID)
  80. - (NSString *)projectID {
  81. return objc_getAssociatedObject(self, @selector(projectID));
  82. }
  83. - (void)setProjectID:(NSString *)projectID {
  84. objc_setAssociatedObject(self, @selector(projectID), projectID, OBJC_ASSOCIATION_COPY_NONATOMIC);
  85. }
  86. @end
  87. /** @fn versionString
  88. @brief Constructs a version string to display.
  89. @param string The version in string form.
  90. @param number The version in number form.
  91. */
  92. static NSString *versionString(const unsigned char *string, const double number) {
  93. return [NSString stringWithFormat:@"\"%s\" (%g)", string, number];
  94. }
  95. /** @fn requestHost
  96. @brief Retrieves the API host for the request class.
  97. @param requestClassName The name of the request class.
  98. */
  99. static NSString *APIHost(NSString *requestClassName) {
  100. return [(id<RequestClass>)NSClassFromString(requestClassName) host];
  101. }
  102. /** @fn truncatedString
  103. @brief Truncates a string under a maximum length.
  104. @param string The original string to be truncated.
  105. @param length The maximum length of the truncated string.
  106. @return The truncated string, which is not longer than @c length.
  107. */
  108. static NSString *truncatedString(NSString *string, NSUInteger length) {
  109. if (string.length <= length) {
  110. return string;
  111. }
  112. NSUInteger half = (length - 3) / 2;
  113. return [NSString stringWithFormat:@"%@...%@",
  114. [string substringToIndex:half],
  115. [string substringFromIndex:string.length - half]];
  116. }
  117. /** @fn hexString
  118. @brief Converts a piece of data into a hexadecimal string.
  119. @param data The raw data.
  120. @return The hexadecimal string representation of the data.
  121. */
  122. static NSString *hexString(NSData *data) {
  123. NSMutableString *string = [NSMutableString stringWithCapacity:data.length * 2];
  124. const unsigned char *bytes = data.bytes;
  125. for (int idx = 0; idx < data.length; ++idx) {
  126. [string appendFormat:@"%02X", (int)bytes[idx]];
  127. }
  128. return string;
  129. }
  130. @implementation SettingsViewController
  131. - (void)viewDidLoad {
  132. [super viewDidLoad];
  133. [self setUpAPIHosts];
  134. [self setUpFirebaseAppOptions];
  135. [self loadTableView];
  136. }
  137. - (IBAction)done:(id)sender {
  138. [self dismissViewControllerAnimated:YES completion:nil];
  139. }
  140. - (void)setUpAPIHosts {
  141. if (gAPIHosts) {
  142. return;
  143. }
  144. gAPIHosts = @{
  145. kIdentityToolkitRequestClassName : @[
  146. APIHost(kIdentityToolkitRequestClassName),
  147. kIdentityToolkitSandboxHost,
  148. ],
  149. kSecureTokenRequestClassName : @[
  150. APIHost(kSecureTokenRequestClassName),
  151. kSecureTokenSandboxHost,
  152. ],
  153. };
  154. }
  155. - (void)setUpFirebaseAppOptions {
  156. if (gFirebaseAppOptions) {
  157. return;
  158. }
  159. int numberOfOptions = sizeof(kGoogleServiceInfoPlists) / sizeof(*kGoogleServiceInfoPlists);
  160. NSMutableArray *appOptions = [[NSMutableArray alloc] initWithCapacity:numberOfOptions];
  161. for (int i = 0; i < numberOfOptions; i++) {
  162. NSString *plistFileName = kGoogleServiceInfoPlists[i];
  163. NSString *plistFilePath = [[NSBundle mainBundle] pathForResource:plistFileName
  164. ofType:@"plist"];
  165. NSDictionary *optionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
  166. FIROptions *options = [[FIROptions alloc]
  167. initWithGoogleAppID:optionsDictionary[@"GOOGLE_APP_ID"]
  168. bundleID:optionsDictionary[@"BUNDLE_ID"]
  169. GCMSenderID:optionsDictionary[@"GCM_SENDER_ID"]
  170. APIKey:optionsDictionary[@"API_KEY"]
  171. clientID:optionsDictionary[@"CLIENT_ID"]
  172. trackingID:optionsDictionary[@"TRACKING_ID"]
  173. androidClientID:optionsDictionary[@"ANDROID_CLIENT_ID"]
  174. databaseURL:optionsDictionary[@"DATABASE_URL"]
  175. storageBucket:optionsDictionary[@"STORAGE_BUCKET"]
  176. deepLinkURLScheme:nil];
  177. options.projectID = optionsDictionary[@"PROJECT_ID"];
  178. [appOptions addObject:options];
  179. }
  180. gFirebaseAppOptions = [appOptions copy];
  181. }
  182. - (void)loadTableView {
  183. __weak typeof(self) weakSelf = self;
  184. _tableViewManager.contents = [StaticContentTableViewContent contentWithSections:@[
  185. [StaticContentTableViewSection sectionWithTitle:@"Versions" cells:@[
  186. [StaticContentTableViewCell cellWithTitle:@"FirebaseAuth"
  187. value:versionString(
  188. FirebaseAuthVersionString, FirebaseAuthVersionNumber)],
  189. ]],
  190. [StaticContentTableViewSection sectionWithTitle:@"Client Identity" cells:@[
  191. [StaticContentTableViewCell cellWithTitle:@"Project"
  192. value:[self currentProjectID]
  193. action:^{
  194. [weakSelf toggleClientProject];
  195. }],
  196. ]],
  197. [StaticContentTableViewSection sectionWithTitle:@"API Hosts" cells:@[
  198. [StaticContentTableViewCell cellWithTitle:@"Identity Toolkit"
  199. value:APIHost(kIdentityToolkitRequestClassName)
  200. action:^{
  201. [weakSelf toggleAPIHostWithRequestClassName:kIdentityToolkitRequestClassName];
  202. }],
  203. [StaticContentTableViewCell cellWithTitle:@"Secure Token"
  204. value:APIHost(kSecureTokenRequestClassName)
  205. action:^{
  206. [weakSelf toggleAPIHostWithRequestClassName:kSecureTokenRequestClassName];
  207. }],
  208. ]],
  209. [StaticContentTableViewSection sectionWithTitle:@"Phone Auth" cells:@[
  210. [StaticContentTableViewCell cellWithTitle:@"APNs Token"
  211. value:[self APNSTokenString]
  212. action:^{
  213. [weakSelf clearAPNSToken];
  214. }],
  215. [StaticContentTableViewCell cellWithTitle:@"App Credential"
  216. value:[self appCredentialString]
  217. action:^{
  218. [weakSelf clearAppCredential];
  219. }],
  220. ]],
  221. ]];
  222. }
  223. /** @fn toggleAPIHostWithRequestClassName:
  224. @brief Toggles the host name of the server that handles RPCs.
  225. @param requestClassName The name of the RPC request class.
  226. */
  227. - (void)toggleAPIHostWithRequestClassName:(NSString *)requestClassName {
  228. NSString *currentHost = APIHost(requestClassName);
  229. NSArray<NSString *> *allHosts = gAPIHosts[requestClassName];
  230. NSString *newHost = allHosts[([allHosts indexOfObject:currentHost] + 1) % allHosts.count];
  231. [(id<RequestClass>)NSClassFromString(requestClassName) setHost:newHost];
  232. [self loadTableView];
  233. }
  234. /** @fn currentProjectID
  235. @brief Returns the the current Firebase project ID.
  236. */
  237. - (NSString *)currentProjectID {
  238. NSString *APIKey = [FIRApp defaultApp].options.APIKey;
  239. for (FIROptions *options in gFirebaseAppOptions) {
  240. if ([options.APIKey isEqualToString:APIKey]) {
  241. return options.projectID;
  242. }
  243. }
  244. return nil;
  245. }
  246. /** @fn toggleClientProject
  247. @brief Toggles the Firebase/Google project this client presents by recreating the default
  248. FIRApp instance with different options.
  249. */
  250. - (void)toggleClientProject {
  251. NSString *APIKey = [FIRApp defaultApp].options.APIKey;
  252. for (NSUInteger i = 0 ; i < gFirebaseAppOptions.count; i++) {
  253. FIROptions *options = gFirebaseAppOptions[i];
  254. if ([options.APIKey isEqualToString:APIKey]) {
  255. __weak typeof(self) weakSelf = self;
  256. [[FIRApp defaultApp] deleteApp:^(BOOL success) {
  257. if (success) {
  258. [FIRInstanceID notifyTokenRefresh]; // b/28967043
  259. dispatch_async(dispatch_get_main_queue(), ^() {
  260. FIROptions *options = gFirebaseAppOptions[(i + 1) % gFirebaseAppOptions.count];
  261. [FIRApp configureWithOptions:options];
  262. [weakSelf loadTableView];
  263. });
  264. }
  265. }];
  266. return;
  267. }
  268. }
  269. }
  270. /** @fn APNSTokenString
  271. @brief Returns a string representing APNS token.
  272. */
  273. - (NSString *)APNSTokenString {
  274. FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
  275. if (!token) {
  276. return @"";
  277. }
  278. return [NSString stringWithFormat:@"%@(%@)",
  279. truncatedString(hexString(token.data), 19),
  280. token.type == FIRAuthAPNSTokenTypeProd ? @"P" : @"S"];
  281. }
  282. /** @fn clearAPNSToken
  283. @brief Clears the saved app credential.
  284. */
  285. - (void)clearAPNSToken {
  286. FIRAuthAPNSToken *token = [FIRAuth auth].tokenManager.token;
  287. if (!token) {
  288. return;
  289. }
  290. NSString *tokenType = token.type == FIRAuthAPNSTokenTypeProd ? @"Production" : @"Sandbox";
  291. NSString *message = [NSString stringWithFormat:@"token: %@\ntype: %@",
  292. hexString(token.data), tokenType];
  293. [self showMessagePromptWithTitle:@"Clear APNs Token?"
  294. message:message
  295. showCancelButton:YES
  296. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  297. if (userPressedOK) {
  298. [FIRAuth auth].tokenManager.token = nil;
  299. [self loadTableView];
  300. }
  301. }];
  302. }
  303. /** @fn appCredentialString
  304. @brief Returns a string representing app credential.
  305. */
  306. - (NSString *)appCredentialString {
  307. FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
  308. if (!credential) {
  309. return @"";
  310. }
  311. return [NSString stringWithFormat:@"%@/%@",
  312. truncatedString(credential.receipt, 13),
  313. truncatedString(credential.secret, 13)];
  314. }
  315. /** @fn clearAppCredential
  316. @brief Clears the saved app credential.
  317. */
  318. - (void)clearAppCredential {
  319. FIRAuthAppCredential *credential = [FIRAuth auth].appCredentialManager.credential;
  320. if (!credential) {
  321. return;
  322. }
  323. NSString *message = [NSString stringWithFormat:@"receipt: %@\nsecret: %@",
  324. credential.receipt, credential.secret];
  325. [self showMessagePromptWithTitle:@"Clear App Credential?"
  326. message:message
  327. showCancelButton:YES
  328. completion:^(BOOL userPressedOK, NSString *_Nullable userInput) {
  329. if (userPressedOK) {
  330. [[FIRAuth auth].appCredentialManager clearCredential];
  331. [self loadTableView];
  332. }
  333. }];
  334. }
  335. @end