aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Auth/Source/FIRAuthNotificationManager.m
blob: 0692562243fab6d0af81175fcc11dd3c9b35c1bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/*
 * Copyright 2017 Google
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "Private/FIRAuthNotificationManager.h"

#import "FIRLogger.h"
#import "Private/FIRAuthAppCredential.h"
#import "Private/FIRAuthAppCredentialManager.h"
#import "FIRAuthGlobalWorkQueue.h"

NS_ASSUME_NONNULL_BEGIN

/** @var kNotificationKey
    @brief The key to locate payload data in the remote notification.
 */
static NSString *const kNotificationDataKey = @"com.google.firebase.auth";

/** @var kNotificationReceiptKey
    @brief The key for the receipt in the remote notification payload data.
 */
static NSString *const kNotificationReceiptKey = @"receipt";

/** @var kNotificationSecretKey
    @brief The key for the secret in the remote notification payload data.
 */
static NSString *const kNotificationSecretKey = @"secret";

/** @var kNotificationProberKey
    @brief The key for marking the prober in the remote notification payload data.
 */
static NSString *const kNotificationProberKey = @"warning";

/** @var kProbingTimeout
    @brief Timeout for probing whether the app delegate forwards the remote notification to us.
 */
static const NSTimeInterval kProbingTimeout = 1;

@implementation FIRAuthNotificationManager {
  /** @var _application
      @brief The application.
   */
  UIApplication *_application;

  /** @var _appCredentialManager
      @brief The object to handle app credentials delivered via notification.
   */
  FIRAuthAppCredentialManager *_appCredentialManager;

  /** @var _hasCheckedNotificationForwarding
      @brief Whether notification forwarding has been checked or not.
   */
  BOOL _hasCheckedNotificationForwarding;

  /** @var _isNotificationBeingForwarded
      @brief Whether or not notification is being forwarded
   */
  BOOL _isNotificationBeingForwarded;

  /** @var _pendingCallbacks
      @brief All pending callbacks while a check is being performed.
   */
  NSMutableArray<FIRAuthNotificationForwardingCallback> *_pendingCallbacks;
}

- (instancetype)initWithApplication:(UIApplication *)application
               appCredentialManager:(FIRAuthAppCredentialManager *)appCredentialManager {
  self = [super init];
  if (self) {
    _application = application;
    _appCredentialManager = appCredentialManager;
    _timeout = kProbingTimeout;
  }
  return self;
}

- (void)checkNotificationForwardingWithCallback:(FIRAuthNotificationForwardingCallback)callback {
  if (_pendingCallbacks) {
    [_pendingCallbacks addObject:callback];
    return;
  }
  if (_hasCheckedNotificationForwarding) {
    callback(_isNotificationBeingForwarded);
    return;
  }
  _hasCheckedNotificationForwarding = YES;
  _pendingCallbacks =
      [[NSMutableArray<FIRAuthNotificationForwardingCallback> alloc] initWithObjects:callback, nil];
  dispatch_async(dispatch_get_main_queue(), ^{
    NSDictionary *proberNotification = @{
      kNotificationDataKey : @{
        kNotificationProberKey : @"This fake notification should be forwarded to Firebase Auth."
      }
    };
    if ([_application.delegate respondsToSelector:
            @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) {
      [_application.delegate application:_application
            didReceiveRemoteNotification:proberNotification
                  fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
    } else if ([_application.delegate respondsToSelector:
                   @selector(application:didReceiveRemoteNotification:)]) {
      [_application.delegate application:_application
            didReceiveRemoteNotification:proberNotification];
    } else {
      FIRLogError(kFIRLoggerAuth, @"I-AUT000015",
                  @"The UIApplicationDelegate must handle remote notifcation for phone number "
                  @"authentication to work.");
    }
  });
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_timeout * NSEC_PER_SEC)),
                               FIRAuthGlobalWorkQueue(), ^{
    [self callBack];
  });
}

- (BOOL)canHandleNotification:(NSDictionary *)notification {
  NSDictionary *data = notification[kNotificationDataKey];
  if ([data isKindOfClass:[NSString class]]) {
    // Deserialize in case the data is a JSON string.
    NSData *JSONData = [((NSString *)data) dataUsingEncoding:NSUTF8StringEncoding];
    data = [NSJSONSerialization JSONObjectWithData:JSONData options:0 error:NULL];
  }
  if (![data isKindOfClass:[NSDictionary class]]) {
    return NO;
  }
  if (data[kNotificationProberKey]) {
    if (!_pendingCallbacks) {
      // The prober notification probably comes from another instance, so pass it along.
      return NO;
    }
    _isNotificationBeingForwarded = YES;
    [self callBack];
    return YES;
  }
  NSString *receipt = data[kNotificationReceiptKey];
  if (![receipt isKindOfClass:[NSString class]]) {
    return NO;
  }
  NSString *secret = data[kNotificationSecretKey];
  if (![receipt isKindOfClass:[NSString class]]) {
    return NO;
  }
  return [_appCredentialManager canFinishVerificationWithReceipt:receipt secret:secret];
}

#pragma mark - Internal methods

/** @fn callBack
    @brief Calls back all pending callbacks with the result of notification forwarding check.
 */
- (void)callBack {
  if (!_pendingCallbacks) {
    return;
  }
  NSArray<FIRAuthNotificationForwardingCallback> *allCallbacks = _pendingCallbacks;
  _pendingCallbacks = nil;
  for (FIRAuthNotificationForwardingCallback callback in allCallbacks) {
    callback(_isNotificationBeingForwarded);
  }
};

@end
NS_ASSUME_NONNULL_END