SettingsViewController.m 13 KB

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