FIRAuthTVService.m 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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 "FIRAuthTVService.h"
  17. #import "FIRAuthTVCode.h"
  18. #import "FIRAuthTVPollRequest.h"
  19. #import "FIRAuthTVPollResult.h"
  20. NSString * const kFIRAuthClientIDKey = @"client_id";
  21. NSString * const kFIRAuthClientSecretKey = @"client_secret";
  22. NSString * const kFIRAuthScopeKey = @"scope";
  23. NSString * const kFIRAuthScope = @"email%20profile";
  24. #warning Fill out the two following fields from the Cloud console.
  25. NSString * const kFIRAuthTVClientID = @"FILL ME OUT";
  26. NSString * const kFIRAuthTVClientSecret = @"FILL ME OUT";
  27. @implementation FIRAuthTVService
  28. - (void)requestAuthorizationCodeWithCompletion:(nonnull FIRAuthTVAuthorizationCallback)callback {
  29. NSURL *url = [NSURL URLWithString:@"https://accounts.google.com/o/oauth2/device/code"];
  30. NSURLRequest *request = [self postRequestWithURL:url
  31. parameters:@{
  32. kFIRAuthClientIDKey: kFIRAuthTVClientID,
  33. kFIRAuthScopeKey: kFIRAuthScope
  34. }];
  35. NSURLSession *session = [NSURLSession sharedSession];
  36. NSURLSessionDataTask *task = [session dataTaskWithRequest:request
  37. completionHandler:^(NSData * _Nullable data,
  38. NSURLResponse * _Nullable response,
  39. NSError * _Nullable error) {
  40. if (error) {
  41. NSLog(@"Error with data task: %@", error.localizedDescription);
  42. // TODO: Customize error.
  43. callback(nil, error);
  44. return;
  45. }
  46. if (!(data && response)) {
  47. // TODO: Customize error with proper fields.
  48. callback(nil, [NSError errorWithDomain:@"com.firebase.auth"
  49. code:1
  50. userInfo:@{ NSLocalizedDescriptionKey: @"Objective-C sucks" }]);
  51. return;
  52. }
  53. // Assume data and response are fine, because Obj-C.
  54. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  55. if ([httpResponse statusCode] == 200) {
  56. FIRAuthTVCode *code = [self tvCodeFromData:data];
  57. callback(code, nil);
  58. } else {
  59. NSLog(@"Something went wrong, status code: %ld", (long)[httpResponse statusCode]);
  60. // TODO: Return customized error.
  61. callback(nil, nil);
  62. }
  63. }];
  64. // Start the task.
  65. [task resume];
  66. }
  67. - (void)pollServersWithCode:(FIRAuthTVCode *)code
  68. successCallback:(void (^)(FIRAuthTVPollResult *))successCallback
  69. failureCallback:(void (^)(NSError *))failureCallback {
  70. // TODO: Check total timeout here as well. This is messy.
  71. NSURL *url = [NSURL URLWithString:@"https://www.googleapis.com/oauth2/v4/token"];
  72. FIRAuthTVPollRequest *pollRequest =
  73. [[FIRAuthTVPollRequest alloc] initWithClientID:kFIRAuthTVClientID
  74. clientSecret:kFIRAuthTVClientSecret
  75. deviceCode:code.deviceCode];
  76. NSURLRequest *request = [self postRequestWithURL:url
  77. parameters:[pollRequest generatedParameters]];
  78. NSURLSession *session = [NSURLSession sharedSession];
  79. NSURLSessionDataTask *task = [session dataTaskWithRequest:request
  80. completionHandler:^(NSData * _Nullable data,
  81. NSURLResponse * _Nullable response,
  82. NSError * _Nullable error) {
  83. if (error) {
  84. NSLog(@"Error polling server: %@",
  85. error.localizedDescription);
  86. // TODO: Customize error.
  87. failureCallback(error);
  88. return;
  89. }
  90. if (!(data && response)) {
  91. // TODO: Customize error with proper fields.
  92. failureCallback([NSError errorWithDomain:@"com.firebase.auth"
  93. code:1
  94. userInfo:@{ NSLocalizedDescriptionKey: @"Objective-C sucks" }]);
  95. return;
  96. }
  97. // Get the dictionary as it's needed to parse the response.
  98. NSError *parsingError = nil;
  99. NSDictionary *resultDictionary = [NSJSONSerialization JSONObjectWithData:data
  100. options:0
  101. error:&parsingError];
  102. if (parsingError) {
  103. NSLog(@"Error parsing data: %@", parsingError);
  104. failureCallback(parsingError);
  105. return;
  106. }
  107. // Handle the retry and bad response errors.
  108. NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
  109. if ([httpResponse statusCode] == 400) {
  110. if ([resultDictionary[@"error"] isEqualToString:@"authorization_pending"]) {
  111. // We're pending! Try again in the timeout time.
  112. dispatch_time_t time =
  113. dispatch_time(DISPATCH_TIME_NOW, code.pollingInterval * NSEC_PER_SEC);
  114. // TODO: Do this off the main queue :P
  115. __weak FIRAuthTVService *weakSelf = self;
  116. dispatch_after(time, dispatch_get_main_queue(), ^{
  117. [weakSelf pollServersWithCode:code
  118. successCallback:successCallback
  119. failureCallback:failureCallback];
  120. });
  121. return;
  122. } else {
  123. failureCallback([NSError errorWithDomain:@"com.firebase.auth"
  124. code:400
  125. userInfo:nil]);
  126. }
  127. return;
  128. }
  129. // Handle other errors.
  130. if ([httpResponse statusCode] != 200) {
  131. failureCallback([NSError errorWithDomain:@"com.firebase.auth"
  132. code:[httpResponse statusCode]
  133. userInfo:nil]);
  134. return;
  135. }
  136. // 200 Status, parse the response.
  137. FIRAuthTVPollResult *result = [[FIRAuthTVPollResult alloc] initWithDictionary:resultDictionary];
  138. successCallback(result);
  139. }];
  140. [task resume];
  141. }
  142. - (NSURLRequest *)postRequestWithURL:(NSURL *)url
  143. parameters:(NSDictionary <NSString *, NSString *>*) parameters {
  144. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
  145. [request setHTTPMethod:@"POST"];
  146. [request addValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
  147. NSString *httpParams = [self parametersFromDictionary: parameters];
  148. [request setHTTPBody:[httpParams dataUsingEncoding:NSUTF8StringEncoding]];
  149. return request;
  150. }
  151. #pragma mark - Convenience Methods
  152. - (NSString *)parametersFromDictionary:(NSDictionary<NSString *, NSString *> *)dict {
  153. NSMutableString *result = [[NSMutableString alloc] init];
  154. NSArray *allKeys = [dict allKeys];
  155. NSInteger keysLeft = allKeys.count;
  156. for (NSString *key in [dict allKeys]) {
  157. [result appendFormat:@"%@=%@", key, dict[key]];
  158. keysLeft -= 1;
  159. if (keysLeft > 0) {
  160. [result appendString:@"&"];
  161. }
  162. }
  163. return result;
  164. }
  165. - (nullable FIRAuthTVCode *)tvCodeFromData:(NSData *)data {
  166. NSError *parsingError = nil;
  167. NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data
  168. options:0
  169. error:&parsingError];
  170. if (parsingError) {
  171. NSLog(@"Error parsing data: %@", parsingError);
  172. return nil;
  173. }
  174. FIRAuthTVCode *code = [[FIRAuthTVCode alloc] initWithDictionary:response];
  175. return code;
  176. }
  177. @end