-
Notifications
You must be signed in to change notification settings - Fork 136
Expand file tree
/
Copy pathauth.cc
More file actions
384 lines (326 loc) · 13.7 KB
/
auth.cc
File metadata and controls
384 lines (326 loc) · 13.7 KB
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/*
* Copyright 2016 Google LLC
*
* 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.
*/
#include "auth/src/include/firebase/auth.h"
#include <assert.h>
#include <stdio.h>
#include <algorithm>
#include <cstdint>
#include <map>
#include <string>
#include "app/src/assert.h"
#include "app/src/cleanup_notifier.h"
#include "app/src/include/firebase/app.h"
#include "app/src/include/firebase/future.h"
#include "app/src/include/firebase/internal/common.h"
#include "app/src/include/firebase/internal/mutex.h"
#include "app/src/include/firebase/internal/platform.h"
#include "app/src/include/firebase/version.h"
#include "app/src/semaphore.h"
#include "app/src/util.h"
#include "auth/src/common.h"
#include "auth/src/data.h"
// Workaround MSVC's incompatible libc headers.
#if FIREBASE_PLATFORM_WINDOWS
#define snprintf _snprintf
#endif // FIREBASE_PLATFORM_WINDOWS
// Register the module initializer.
FIREBASE_APP_REGISTER_CALLBACKS(
auth, { return ::firebase::kInitResultSuccess; },
{
// Nothing to tear down.
},
false);
namespace firebase {
namespace auth {
DEFINE_FIREBASE_VERSION_STRING(FirebaseAuth);
// The global map of Apps to Auths. Each App can have at most one Auth,
// and an Auth requires an App. So, the user acquires the Auth via an App
// reference.
// TODO(jsanmiya): Ensure all the Auths are destroyed on shutdown.
std::map<App*, Auth*> g_auths;
Mutex* g_auths_mutex = new Mutex();
// static
Auth* Auth::GetAuth(App* app, InitResult* init_result_out) {
MutexLock lock(*g_auths_mutex);
// Return the Auth if it already exists.
Auth* existing_auth = FindAuth(app);
if (existing_auth) {
if (init_result_out != nullptr) *init_result_out = kInitResultSuccess;
// Log heartbeat data when instance getters are called.
// See go/firebase-platform-logging-design for more information.
LogHeartbeat(existing_auth);
return existing_auth;
}
// Create the platform dependent version of Auth.
void* auth_impl = CreatePlatformAuth(app);
if (!auth_impl) return nullptr;
// Create a new Auth and initialize.
Auth* auth = new Auth(app, auth_impl);
LogDebug("Creating Auth %p for App %p", auth, app);
// Stick it in the global map so we remember it, and can delete it on
// shutdown.
g_auths[app] = auth;
if (init_result_out) *init_result_out = kInitResultSuccess;
return auth;
}
// static
Auth* Auth::FindAuth(App* app) {
MutexLock lock(*g_auths_mutex);
// Return the Auth if it already exists.
std::map<App*, Auth*>::iterator it = g_auths.find(app);
if (it != g_auths.end()) {
return it->second;
}
return nullptr;
}
// Auth uses the pimpl mechanism to hide internal data types from the interface.
Auth::Auth(App* app, void* auth_impl) : auth_data_(new AuthData) {
FIREBASE_ASSERT(app != nullptr && auth_impl != nullptr);
auth_data_->app = app;
auth_data_->auth = this;
auth_data_->auth_impl = auth_impl;
auth_data_->user_impl = nullptr;
InitPlatformAuth(auth_data_);
static const char* kApiIdentifier = "Auth";
auth_data_->future_api_id = CreateApiIdentifier(kApiIdentifier, this);
// Cleanup this object if app is destroyed.
CleanupNotifier* notifier = CleanupNotifier::FindByOwner(app);
assert(notifier);
notifier->RegisterObject(this, [](void* object) {
Auth* auth = reinterpret_cast<Auth*>(object);
LogWarning(
"Auth object 0x%08x should be deleted before the App 0x%08x it "
"depends upon.",
static_cast<int>(reinterpret_cast<intptr_t>(auth)),
static_cast<int>(reinterpret_cast<intptr_t>(auth->auth_data_->app)));
auth->DeleteInternal();
});
}
void Auth::DeleteInternal() {
MutexLock lock(*g_auths_mutex);
if (!auth_data_) return;
{
MutexLock destructing_lock(auth_data_->destructing_mutex);
auth_data_->destructing = true;
}
// Make sure we don't have any pending futures in flight before we disappear.
while (!auth_data_->future_impl.IsSafeToDelete()) {
internal::Sleep(100);
}
CleanupNotifier* notifier = CleanupNotifier::FindByOwner(auth_data_->app);
assert(notifier);
notifier->UnregisterObject(this);
int num_auths_remaining = 0;
{
// Remove `this` from the g_auths map.
// The mapping is 1:1, so we should only ever delete one.
for (auto it = g_auths.begin(); it != g_auths.end(); ++it) {
if (it->second == this) {
LogDebug("Deleting Auth %p for App %p", this, it->first);
g_auths.erase(it);
break;
}
}
num_auths_remaining = g_auths.size();
}
auth_data_->ClearListeners();
// If this is the last Auth instance to be cleaned up, also clean up data for
// Credentials.
if (num_auths_remaining == 0) {
CleanupCredentialFutureImpl();
}
// Destroy the platform-specific object.
DestroyPlatformAuth(auth_data_);
// Delete the pimpl data.
delete auth_data_;
auth_data_ = nullptr;
}
Auth::~Auth() { DeleteInternal(); }
// Always non-nullptr since set in constructor.
App& Auth::app() {
FIREBASE_ASSERT(auth_data_ != nullptr);
return *auth_data_->app;
}
template <typename T>
static bool PushBackIfMissing(const T& entry, std::vector<T>* v) {
auto it = std::find(v->begin(), v->end(), entry);
if (it != v->end()) return false;
v->push_back(entry);
return true;
}
// Store a unique listener of type T in a listener_vector and unique auth
// object in auth_vector. Both vectors must be in sync, i.e addition must
// either succeed or fail otherwise this method asserts.
// Return whether the listener is added.
template <typename T>
static bool AddListener(T listener, std::vector<T>* listener_vector, Auth* auth,
std::vector<Auth*>* auth_vector) {
// Add to array of listeners if not already there.
const bool listener_added = PushBackIfMissing(listener, listener_vector);
// Add auth to array of Auths if not already there.
const bool auth_added = PushBackIfMissing(auth, auth_vector);
// The listener and Auth should either point at each other or not point
// at each other.
FIREBASE_ASSERT_RETURN(false, listener_added == auth_added);
(void)auth_added;
return listener_added;
}
void Auth::AddAuthStateListener(AuthStateListener* listener) {
if (!auth_data_) return;
// Would have to lock mutex till the method ends to protect on race
// conditions.
MutexLock lock(auth_data_->listeners_mutex);
bool added =
AddListener(listener, &auth_data_->listeners, this, &listener->auths_);
// If the listener is registered successfully and persistent cache has been
// loaded, trigger OnAuthStateChanged() immediately. Otherwise, wait until
// the cache is loaded, through AuthStateListener event.
if (added && !auth_data_->persistent_cache_load_pending) {
listener->OnAuthStateChanged(this);
}
}
void Auth::AddIdTokenListener(IdTokenListener* listener) {
if (!auth_data_) return;
// Would have to lock mutex till the method ends to protect on race
// conditions.
MutexLock lock(auth_data_->listeners_mutex);
bool added = AddListener(listener, &auth_data_->id_token_listeners, this,
&listener->auths_);
// AddListener is valid even if the listener is already registered.
// This makes sure that we only increase the reference count if a listener
// was actually added.
if (added) {
// If the listener is registered successfully and persistent cache has been
// loaded, trigger OnAuthStateChanged() immediately. Otherwise, wait until
// the cache is loaded, through AuthStateListener event
if (!auth_data_->persistent_cache_load_pending) {
listener->OnIdTokenChanged(this);
}
EnableTokenAutoRefresh(auth_data_);
}
}
template <typename T>
static bool ReplaceEntryWithBack(const T& entry, std::vector<T>* v) {
auto it = std::find(v->begin(), v->end(), entry);
if (it == v->end()) return false;
// If it's not already the back element, move/copy the back element onto it.
if (&(*it) != &(v->back())) {
#if defined(FIREBASE_USE_MOVE_OPERATORS)
*it = std::move(v->back());
#else
*it = v->back();
#endif // defined(FIREBASE_USE_MOVE_OPERATORS)
}
v->pop_back();
return true;
}
// Remove a listener of type T from listener_vector and unique auth object in
// auth_vector. Both vectors must be in sync, i.e addition must either
// succeed or fail otherwise this method asserts.
template <typename T>
static void RemoveListener(T listener, std::vector<T>* listener_vector,
Auth* auth, std::vector<Auth*>* auth_vector,
Mutex* mutex) {
MutexLock lock(*mutex);
// Remove `listener` from our vector of listeners.
ReplaceEntryWithBack(listener, listener_vector);
// Remove this Auth from the listener's vector of Auths.
// This ensures the listener doesn't try to unregister itself again when it is
// destroyed.
ReplaceEntryWithBack(auth, auth_vector);
}
void Auth::RemoveAuthStateListener(AuthStateListener* listener) {
if (!auth_data_) return;
RemoveListener(listener, &auth_data_->listeners, this, &listener->auths_,
&auth_data_->listeners_mutex);
}
void Auth::RemoveIdTokenListener(IdTokenListener* listener) {
if (!auth_data_) return;
int listener_count = auth_data_->id_token_listeners.size();
RemoveListener(listener, &auth_data_->id_token_listeners, this,
&listener->auths_, &auth_data_->listeners_mutex);
// RemoveListener is valid even if the listener is not registered.
// This makes sure that we only decrease the reference count if a listener
// was actually removed.
if (auth_data_->id_token_listeners.size() < listener_count) {
DisableTokenAutoRefresh(auth_data_);
}
}
AuthStateListener::~AuthStateListener() {
// Removing the listener edits the auths list, hence the while loop.
while (!auths_.empty()) {
(*auths_.begin())->RemoveAuthStateListener(this);
}
}
IdTokenListener::~IdTokenListener() {
// Removing the listener edits the auths list, hence the while loop.
while (!auths_.empty()) {
(*auths_.begin())->RemoveIdTokenListener(this);
}
}
template <typename T>
static inline bool VectorContains(const T& entry, const std::vector<T>& v) {
return std::find(v.begin(), v.end(), entry) != v.end();
}
// Generate a method that notifies a vector of listeners using listeners_method.
#define AUTH_NOTIFY_LISTENERS(notify_method_name, notification_name, \
listeners_vector, notification_method) \
void notify_method_name(AuthData* auth_data) { \
MutexLock lock(auth_data->listeners_mutex); \
\
/* Auth should have loaded persistent cache if exists when the listener */ \
/* event is triggered for the first time. */ \
auth_data->persistent_cache_load_pending = false; \
\
/* Make a copy of the listener list in case it gets modified during */ \
/* notification_method(). Note that modification is possible because */ \
/* the same thread is allowed to reaquire the `listeners_mutex` */ \
/* multiple times. */ \
auto listeners = auth_data->listeners_vector; \
LogDebug(notification_name " changed. Notifying %d listeners.", \
listeners.size()); \
\
for (auto it = listeners.begin(); it != listeners.end(); ++it) { \
/* Skip any listeners that have been removed in notification_method() */ \
/* on earlier iterations of this loop. */ \
auto* listener = *it; \
if (!VectorContains(listener, auth_data->listeners_vector)) { \
continue; \
} \
\
/* Notify the listener. */ \
listener->notification_method(auth_data->auth); \
} \
}
AUTH_NOTIFY_LISTENERS(NotifyAuthStateListeners, "Auth state", listeners,
OnAuthStateChanged);
AUTH_NOTIFY_LISTENERS(NotifyIdTokenListeners, "ID token", id_token_listeners,
OnIdTokenChanged);
AUTH_RESULT_FN(Auth, FetchProvidersForEmail, Auth::FetchProvidersResult)
AUTH_RESULT_FN(Auth, SendPasswordResetEmail, void)
AUTH_RESULT_FN(Auth, SignInWithCustomToken, AuthResult)
AUTH_RESULT_FN(Auth, SignInAndRetrieveDataWithCredential, AuthResult)
AUTH_RESULT_FN(Auth, SignInAnonymously, AuthResult)
AUTH_RESULT_FN(Auth, SignInWithEmailAndPassword, AuthResult)
AUTH_RESULT_FN(Auth, CreateUserWithEmailAndPassword, AuthResult)
AuthError Auth::UseUserAccessGroup(const char* group_id) {
if (!auth_data_) {
return kAuthErrorUninitialized;
}
return UseUserAccessGroupInternal(auth_data_, group_id);
}
} // namespace auth
} // namespace firebase