diff options
author | zxu <zxu@google.com> | 2018-02-09 14:28:29 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-09 14:28:29 -0500 |
commit | d70c23ece0abf7e1c00166e26fa89a670d34a740 (patch) | |
tree | a59b6eb10bd3394fee37cdf59bee0b666b92b3f5 | |
parent | 633eb7bb8bbce2d31d682bf5255d9ef5a97a29c5 (diff) |
port Firestore Auth module in C++ (#733)
* Implement firestore/auth/user
* add user to project and some fixes
* implement firestore/auth/{credentials_provider,empty_credentials_provider}
* implement firestore/auth/firebase_credentials_provider
* refactoring firebase_credentials_provider and add (disabled but working) unit test
* add auth test to project
* address changes
* small fix to style and project
* fix the firebase_credentials_provider_test
* fix style
* address changes
* revert the change to static mutex_
* remove my custom plist path
* fix style
* address changes
* refactoring FirebaseCredentialsProvider to fix the issue w.r.t. auth global dispatch queue
* add /*force_refresh=*/ tag to bool literal for style purpose
* Use a shared_ptr/weak_ptr handoff on FirebaseCredentialsProvider (#778)
* Revert "refactoring FirebaseCredentialsProvider to fix the issue w.r.t. auth global dispatch queue"
This reverts commit 87175a4146267d403a774f138b85f8d3b532fa4b.
* Use a shared_ptr/weak_ptr handoff on FirebaseCredentialsProvider
This avoids any problems with callsbacks retaining pointers to objects
destroyed by a C++ destructor
20 files changed, 1063 insertions, 0 deletions
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 8d99eb6..6ba43bd 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -141,6 +141,11 @@ AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; AB38D93020236E21000A432D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; + ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; }; + ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93620239689000A432D /* empty_credentials_provider_test.cc */; }; + ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93220239654000A432D /* user_test.cc */; }; + ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; }; + ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; @@ -336,8 +341,13 @@ AB380D01201BC69F00D97691 /* bits_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bits_test.cc; path = ../../core/test/firebase/firestore/util/bits_test.cc; sourceTree = "<group>"; }; AB380D03201BC6E400D97691 /* ordered_code_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ordered_code_test.cc; path = ../../core/test/firebase/firestore/util/ordered_code_test.cc; sourceTree = "<group>"; }; AB38D92E20235D22000A432D /* database_info_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_info_test.cc; sourceTree = "<group>"; }; + AB38D93220239654000A432D /* user_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = user_test.cc; sourceTree = "<group>"; }; + AB38D9342023966E000A432D /* credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = credentials_provider_test.cc; sourceTree = "<group>"; }; + AB38D93620239689000A432D /* empty_credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = empty_credentials_provider_test.cc; sourceTree = "<group>"; }; AB71064B201FA60300344F18 /* database_id_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_id_test.cc; sourceTree = "<group>"; }; AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = geo_point_test.cc; path = ../../core/test/firebase/firestore/geo_point_test.cc; sourceTree = "<group>"; }; + ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = token_test.cc; sourceTree = "<group>"; }; + ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = firebase_credentials_provider_test.mm; sourceTree = "<group>"; }; ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = "<group>"; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = "<group>"; }; @@ -430,6 +440,7 @@ 54764FAC1FAA0C390085E60A /* GoogleTests */ = { isa = PBXGroup; children = ( + AB38D9312023962A000A432D /* auth */, AB380CF7201937B800D97691 /* core */, 54EB764B202277970088B8F3 /* immutable */, AB356EF5200E9D1A0089B766 /* model */, @@ -589,6 +600,19 @@ path = ../../core/test/firebase/firestore/core; sourceTree = "<group>"; }; + AB38D9312023962A000A432D /* auth */ = { + isa = PBXGroup; + children = ( + AB38D9342023966E000A432D /* credentials_provider_test.cc */, + AB38D93620239689000A432D /* empty_credentials_provider_test.cc */, + ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */, + ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */, + AB38D93220239654000A432D /* user_test.cc */, + ); + name = auth; + path = ../../core/test/firebase/firestore/auth; + sourceTree = "<group>"; + }; DE0761E51F2FE611003233AF /* SwiftBuildTest */ = { isa = PBXGroup; children = ( @@ -1252,7 +1276,9 @@ 5492E058202154AB00B64F25 /* FSTAPIHelpers.mm in Sources */, AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */, 5492E0A82021552D00B64F25 /* FSTLevelDBLocalStoreTests.mm in Sources */, + ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */, 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */, + ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */, DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */, B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */, 5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */, @@ -1272,6 +1298,7 @@ 5492E0AC2021552D00B64F25 /* FSTMutationQueueTests.mm in Sources */, 5492E056202154AB00B64F25 /* FIRFieldPathTests.mm in Sources */, 5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */, + ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */, AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */, AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */, 5492E0AD2021552D00B64F25 /* FSTMemoryMutationQueueTests.mm in Sources */, @@ -1296,8 +1323,10 @@ 5492E068202154B900B64F25 /* FSTQueryTests.mm in Sources */, 5492E0AB2021552D00B64F25 /* StringViewTests.mm in Sources */, 5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */, + ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */, ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, 5492E0AE2021552D00B64F25 /* FSTLevelDBQueryCacheTests.mm in Sources */, + ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */, 5492E059202154AB00B64F25 /* FIRQuerySnapshotTests.mm in Sources */, 5492E050202154AA00B64F25 /* FIRCollectionReferenceTests.mm in Sources */, 5492E0A02021552D00B64F25 /* FSTLevelDBMutationQueueTests.mm in Sources */, diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index 2fc88c6..e70647d 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -13,6 +13,7 @@ # limitations under the License. add_subdirectory(src/firebase/firestore) +add_subdirectory(src/firebase/firestore/auth) add_subdirectory(src/firebase/firestore/core) add_subdirectory(src/firebase/firestore/immutable) add_subdirectory(src/firebase/firestore/model) @@ -20,6 +21,7 @@ add_subdirectory(src/firebase/firestore/remote) add_subdirectory(src/firebase/firestore/util) add_subdirectory(test/firebase/firestore) +add_subdirectory(test/firebase/firestore/auth) add_subdirectory(test/firebase/firestore/core) add_subdirectory(test/firebase/firestore/immutable) add_subdirectory(test/firebase/firestore/model) diff --git a/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt new file mode 100644 index 0000000..2241fae --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright 2018 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. + +cc_library( + firebase_firestore_auth_base + SOURCES + credentials_provider.cc + credentials_provider.h + token.cc + token.h + user.cc + user.h + DEPENDS + absl_strings + firebase_firestore_util +) + +cc_library( + firebase_firestore_auth_apple + SOURCES + firebase_credentials_provider_apple.h + firebase_credentials_provider_apple.mm + DEPENDS + FirebaseCore + firebase_firestore_auth_base + EXCLUDE_FROM_ALL +) + +if(APPLE) + list(APPEND AUTH_DEPENDS firebase_firestore_auth_apple) +endif() + +cc_library( + firebase_firestore_auth + SOURCES + empty_credentials_provider.cc + empty_credentials_provider.h + DEPENDS + ${AUTH_DEPENDS} + firebase_firestore_auth_base +) diff --git a/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc b/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc new file mode 100644 index 0000000..0301944 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/credentials_provider.cc @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" + +namespace firebase { +namespace firestore { +namespace auth { + +CredentialsProvider::CredentialsProvider() : user_change_listener_(nullptr) { +} + +CredentialsProvider::~CredentialsProvider() { +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/auth/credentials_provider.h b/Firestore/core/src/firebase/firestore/auth/credentials_provider.h new file mode 100644 index 0000000..2a52c99 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/credentials_provider.h @@ -0,0 +1,79 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_ + +#include <functional> +#include <string> + +#include "Firestore/core/src/firebase/firestore/auth/token.h" +#include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace auth { + +// `TokenErrorListener` is a listener that gets a token or an error. +// token: An auth token as a string, or nullptr if error occurred. +// error: The error if one occurred, or else nullptr. +typedef std::function<void(const Token& token, const absl::string_view error)> + TokenListener; + +// Listener notified with a User change. +typedef std::function<void(const User& user)> UserChangeListener; + +/** + * Provides methods for getting the uid and token for the current user and + * listen for changes. + */ +class CredentialsProvider { + public: + CredentialsProvider(); + + virtual ~CredentialsProvider(); + + /** + * Requests token for the current user, optionally forcing a refreshed token + * to be fetched. + */ + virtual void GetToken(bool force_refresh, TokenListener completion) = 0; + + /** + * Sets the listener to be notified of user changes (sign-in / sign-out). It + * is immediately called once with the initial user. + * + * Call with nullptr to remove previous listener. + */ + virtual void SetUserChangeListener(UserChangeListener listener) = 0; + + protected: + /** + * A listener to be notified of user changes (sign-in / sign-out). It is + * immediately called once with the initial user. + * + * Note that this block will be called back on an arbitrary thread that is not + * the normal Firestore worker thread. + */ + UserChangeListener user_change_listener_; +}; + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_CREDENTIALS_PROVIDER_H_ diff --git a/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc new file mode 100644 index 0000000..6ee7f61 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.cc @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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. + */ + +#define UNUSED(x) (void)(x) + +#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h" + +namespace firebase { +namespace firestore { +namespace auth { + +void EmptyCredentialsProvider::GetToken(bool force_refresh, + TokenListener completion) { + UNUSED(force_refresh); + if (completion) { + completion({"", User::Unauthenticated()}, ""); + } +} + +void EmptyCredentialsProvider::SetUserChangeListener( + UserChangeListener listener) { + if (listener) { + listener(User::Unauthenticated()); + } +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h new file mode 100644 index 0000000..55b3cc6 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h @@ -0,0 +1,37 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_ + +#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" + +namespace firebase { +namespace firestore { +namespace auth { + +/** `EmptyCredentialsProvider` always yields an empty token. */ +class EmptyCredentialsProvider : public CredentialsProvider { + public: + void GetToken(bool force_refresh, TokenListener completion) override; + void SetUserChangeListener(UserChangeListener listener) override; +}; + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_EMPTY_CREDENTIALS_PROVIDER_H_ diff --git a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h new file mode 100644 index 0000000..65c4c65 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h @@ -0,0 +1,112 @@ +/* + * Copyright 2018 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. + */ + +// Right now, FirebaseCredentialsProvider only support APPLE build. +#if !defined(__OBJC__) +#error "This header only supports Objective-C++." +#endif // !defined(__OBJC__) + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_ + +#import <Foundation/Foundation.h> + +#include <memory> +#include <mutex> // NOLINT(build/c++11) + +#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" +#include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "absl/strings/string_view.h" + +@class FIRApp; + +namespace firebase { +namespace firestore { +namespace auth { + +/** + * `FirebaseCredentialsProvider` uses Firebase Auth via `FIRApp` to get an auth + * token. + * + * NOTE: To simplify the implementation, it requires that you set + * `userChangeListener` with a non-`nil` value no more than once and don't call + * `getTokenForcingRefresh:` after setting it to `nil`. + * + * This class must be implemented in a thread-safe manner since it is accessed + * from the thread backing our internal worker queue and the callbacks from + * FIRAuth will be executed on an arbitrary different thread. + * + * For non-Apple desktop build, this is right now just a stub. + */ +class FirebaseCredentialsProvider : public CredentialsProvider { + public: + // TODO(zxu123): Provide a ctor to accept the C++ Firebase Games App, which + // deals all platforms. Right now, only works for FIRApp*. + /** + * Initializes a new FirebaseCredentialsProvider. + * + * @param app The Firebase app from which to get credentials. + */ + explicit FirebaseCredentialsProvider(FIRApp* app); + + ~FirebaseCredentialsProvider() override; + + void GetToken(bool force_refresh, TokenListener completion) override; + + void SetUserChangeListener(UserChangeListener listener) override; + + private: + /** + * Most contents of the FirebaseCredentialProvider are kept in this + * Contents object and pointed to with a shared pointer. Callbacks + * registered with FirebaseAuth use weak pointers to the Contents to + * avoid races between notifications arriving and C++ object destruction. + */ + struct Contents { + Contents(FIRApp* app, const absl::string_view uid) + : app(app), current_user(uid), mutex() { + } + + const FIRApp* app; + + /** + * The current user as reported to us via our AuthStateDidChangeListener. + */ + User current_user; + + /** + * Counter used to detect if the user changed while a + * -getTokenForcingRefresh: request was outstanding. + */ + int user_counter = 0; + + std::mutex mutex; + }; + + /** + * Handle used to stop receiving auth changes once userChangeListener is + * removed. + */ + id<NSObject> auth_listener_handle_; + + std::shared_ptr<Contents> contents_; +}; + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_FIREBASE_CREDENTIALS_PROVIDER_APPLE_H_ diff --git a/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm new file mode 100644 index 0000000..f463958 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm @@ -0,0 +1,134 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h" + +#import <FirebaseCore/FIRApp.h> +#import <FirebaseCore/FIRAppInternal.h> +#import <FirebaseCore/FIROptionsInternal.h> + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" + +namespace firebase { +namespace firestore { +namespace auth { + +FirebaseCredentialsProvider::FirebaseCredentialsProvider(FIRApp* app) + : contents_( + std::make_shared<Contents>(app, util::MakeStringView([app getUID]))) { + std::weak_ptr<Contents> weak_contents = contents_; + + auth_listener_handle_ = [[NSNotificationCenter defaultCenter] + addObserverForName:FIRAuthStateDidChangeInternalNotification + object:nil + queue:nil + usingBlock:^(NSNotification* notification) { + std::shared_ptr<Contents> contents = weak_contents.lock(); + if (!contents) { + return; + } + + std::unique_lock<std::mutex> lock(contents->mutex); + NSDictionary<NSString*, id>* user_info = notification.userInfo; + + // ensure we're only notifiying for the current app. + FIRApp* notified_app = + user_info[FIRAuthStateDidChangeInternalNotificationAppKey]; + if (![contents->app isEqual:notified_app]) { + return; + } + + NSString* user_id = + user_info[FIRAuthStateDidChangeInternalNotificationUIDKey]; + User new_user(util::MakeStringView(user_id)); + if (new_user != contents->current_user) { + contents->current_user = new_user; + contents->user_counter++; + UserChangeListener listener = user_change_listener_; + if (listener) { + listener(contents->current_user); + } + } + }]; +} + +FirebaseCredentialsProvider::~FirebaseCredentialsProvider() { + if (auth_listener_handle_) { + // Even though iOS 9 (and later) and macOS 10.11 (and later) keep a weak + // reference to the observer so we could avoid this removeObserver call, we + // still support iOS 8 which requires it. + [[NSNotificationCenter defaultCenter] removeObserver:auth_listener_handle_]; + } +} + +void FirebaseCredentialsProvider::GetToken(bool force_refresh, + TokenListener completion) { + FIREBASE_ASSERT_MESSAGE(auth_listener_handle_, + "GetToken cannot be called after listener removed."); + + // Take note of the current value of the userCounter so that this method can + // fail if there is a user change while the request is outstanding. + int initial_user_counter = contents_->user_counter; + + std::weak_ptr<Contents> weak_contents = contents_; + void (^get_token_callback)(NSString*, NSError*) = ^( + NSString* _Nullable token, NSError* _Nullable error) { + std::shared_ptr<Contents> contents = weak_contents.lock(); + if (!contents) { + return; + } + + std::unique_lock<std::mutex> lock(contents->mutex); + if (initial_user_counter != contents->user_counter) { + // Cancel the request since the user changed while the request was + // outstanding so the response is likely for a previous user (which + // user, we can't be sure). + completion({"", User::Unauthenticated()}, + "getToken aborted due to user change."); + } else { + completion( + {util::MakeStringView(token), contents->current_user}, + error == nil ? "" : util::MakeStringView(error.localizedDescription)); + } + }; + + [contents_->app getTokenForcingRefresh:force_refresh + withCallback:get_token_callback]; +} + +void FirebaseCredentialsProvider::SetUserChangeListener( + UserChangeListener listener) { + std::unique_lock<std::mutex> lock(contents_->mutex); + if (listener) { + FIREBASE_ASSERT_MESSAGE(!user_change_listener_, + "set user_change_listener twice!"); + // Fire initial event. + listener(contents_->current_user); + } else { + FIREBASE_ASSERT_MESSAGE(auth_listener_handle_, + "removed user_change_listener twice!"); + FIREBASE_ASSERT_MESSAGE(user_change_listener_, + "user_change_listener removed without being set!"); + [[NSNotificationCenter defaultCenter] removeObserver:auth_listener_handle_]; + auth_listener_handle_ = nil; + } + user_change_listener_ = listener; +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/auth/token.cc b/Firestore/core/src/firebase/firestore/auth/token.cc new file mode 100644 index 0000000..0618ddb --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/token.cc @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/token.h" + +namespace firebase { +namespace firestore { +namespace auth { + +Token::Token(const absl::string_view token, const User& user) + : token_(token), user_(user) { +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/auth/token.h b/Firestore/core/src/firebase/firestore/auth/token.h new file mode 100644 index 0000000..f3b7363 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/token.h @@ -0,0 +1,68 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_ + +#include <string> + +#include "Firestore/core/src/firebase/firestore/auth/user.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace auth { + +/** + * The current User and the authentication token provided by the underlying + * authentication mechanism. This is the result of calling + * CredentialsProvider::GetToken(). + * + * ## Portability notes: no TokenType on iOS + * + * The TypeScript client supports 1st party Oauth tokens (for the Firebase + * Console to auth as the developer) and OAuth2 tokens for the node.js sdk to + * auth with a service account. We don't have plans to support either case on + * mobile so there's no TokenType here. + */ +// TODO(zxu123): Make this support token-type for desktop workflow. +class Token { + public: + Token(const absl::string_view token, const User& user); + + /** The actual raw token. */ + const std::string& token() const { + return token_; + } + + /** + * The user with which the token is associated (used for persisting user + * state on disk, etc.). + */ + const User& user() const { + return user_; + } + + private: + const std::string token_; + const User user_; +}; + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_TOKEN_H_ diff --git a/Firestore/core/src/firebase/firestore/auth/user.cc b/Firestore/core/src/firebase/firestore/auth/user.cc new file mode 100644 index 0000000..f442d7b --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/user.cc @@ -0,0 +1,39 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/user.h" + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace auth { + +User::User() : uid_(), is_authenticated_(false) { +} + +User::User(const absl::string_view uid) : uid_(uid), is_authenticated_(true) { + FIREBASE_ASSERT(!uid.empty()); +} + +const User& User::Unauthenticated() { + static const User kUnauthenticated; + return kUnauthenticated; +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/auth/user.h b/Firestore/core/src/firebase/firestore/auth/user.h new file mode 100644 index 0000000..58b8b55 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/auth/user.h @@ -0,0 +1,76 @@ +/* + * Copyright 2018 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. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ + +#include <string> + +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace auth { + +/** + * Simple wrapper around a nullable UID. Mostly exists to make code more + * readable and for compatibility with other clients where map keys cannot be + * null. + */ +class User { + public: + /** Construct an unauthenticated user. */ + User(); + + /** Construct an authenticated user with the given UID. */ + explicit User(const absl::string_view uid); + + const std::string& uid() const { + return uid_; + } + + // PORTING NOTE: Here use more clear naming is_authenticated() instead of + // is_unauthenticated(). + bool is_authenticated() const { + return is_authenticated_; + } + + /** Returns an unauthenticated instance. */ + static const User& Unauthenticated(); + + User& operator=(const User& other) = default; + + friend bool operator==(const User& lhs, const User& rhs); + + private: + std::string uid_; + bool is_authenticated_; +}; + +inline bool operator==(const User& lhs, const User& rhs) { + return lhs.is_authenticated_ == rhs.is_authenticated_ && + (!lhs.is_authenticated_ || lhs.uid_ == rhs.uid_); +} + +inline bool operator!=(const User& lhs, const User& rhs) { + return !(lhs == rhs); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_AUTH_USER_H_ diff --git a/Firestore/core/src/firebase/firestore/util/firebase_assert.h b/Firestore/core/src/firebase/firestore/util/firebase_assert.h index 993f27a..76768e6 100644 --- a/Firestore/core/src/firebase/firestore/util/firebase_assert.h +++ b/Firestore/core/src/firebase/firestore/util/firebase_assert.h @@ -91,6 +91,17 @@ FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(condition, expression, __VA_ARGS__) #endif // defined(NDEBUG) +// Assert expression is true otherwise display the specified message and +// abort. +#define FIREBASE_ASSERT_MESSAGE(expression, ...) \ + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(expression, expression, __VA_ARGS__) + +// Assert expression is true otherwise display the specified message and +// abort. Compiled out of release builds. +#define FIREBASE_DEV_ASSERT_MESSAGE(expression, ...) \ + FIREBASE_DEV_ASSERT_MESSAGE_WITH_EXPRESSION(expression, expression, \ + __VA_ARGS__) + namespace firebase { namespace firestore { namespace util { diff --git a/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt b/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt new file mode 100644 index 0000000..f470bd7 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright 2018 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. + +cc_test( + firebase_firestore_auth_test + SOURCES + credentials_provider_test.cc + empty_credentials_provider_test.cc + token_test.cc + user_test.cc + DEPENDS + firebase_firestore_auth +) + +if(APPLE) + cc_test( + firebase_firestore_auth_apple_test + SOURCES + firebase_credentials_provider_test.mm + DEPENDS + firebase_firestore_auth_apple + ) +endif(APPLE) diff --git a/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc b/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc new file mode 100644 index 0000000..1748422 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/credentials_provider_test.cc @@ -0,0 +1,53 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +#define UNUSED(x) (void)(x) + +TEST(CredentialsProvider, Typedef) { + TokenListener token_listener = [](const Token& token, + const absl::string_view error) { + UNUSED(token); + UNUSED(error); + }; + EXPECT_NE(nullptr, token_listener); + EXPECT_TRUE(token_listener); + + token_listener = nullptr; + EXPECT_EQ(nullptr, token_listener); + EXPECT_FALSE(token_listener); + + UserChangeListener user_change_listener = [](const User& user) { + UNUSED(user); + }; + EXPECT_NE(nullptr, user_change_listener); + EXPECT_TRUE(user_change_listener); + + user_change_listener = nullptr; + EXPECT_EQ(nullptr, user_change_listener); + EXPECT_FALSE(user_change_listener); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc b/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc new file mode 100644 index 0000000..123f952 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/empty_credentials_provider_test.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +TEST(EmptyCredentialsProvider, GetToken) { + EmptyCredentialsProvider credentials_provider; + credentials_provider.GetToken( + /*force_refresh=*/true, + [](const Token& token, const absl::string_view error) { + EXPECT_EQ("", token.token()); + const User& user = token.user(); + EXPECT_EQ("", user.uid()); + EXPECT_FALSE(user.is_authenticated()); + EXPECT_EQ("", error); + }); +} + +TEST(EmptyCredentialsProvider, SetListener) { + EmptyCredentialsProvider credentials_provider; + credentials_provider.SetUserChangeListener([](const User& user) { + EXPECT_EQ("", user.uid()); + EXPECT_FALSE(user.is_authenticated()); + }); + + credentials_provider.SetUserChangeListener(nullptr); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm b/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm new file mode 100644 index 0000000..8d2b361 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/firebase_credentials_provider_test.mm @@ -0,0 +1,98 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.h" + +#import <FirebaseCore/FIRApp.h> +#import <FirebaseCore/FIRAppInternal.h> +#import <FirebaseCore/FIROptionsInternal.h> + +#include "Firestore/core/src/firebase/firestore/util/string_apple.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +// TODO(zxu123): Make this an integration test and get infos from environment. +// Set a .plist file here to enable the test-case. +static NSString* const kPlist = @""; + +class FirebaseCredentialsProviderTest : public ::testing::Test { + protected: + void SetUp() override { + app_ready_ = false; + if (![kPlist hasSuffix:@".plist"]) { + return; + } + + static dispatch_once_t once_token; + dispatch_once(&once_token, ^{ + FIROptions* options = [[FIROptions alloc] initWithContentsOfFile:kPlist]; + [FIRApp configureWithOptions:options]; + }); + + // Set getUID implementation. + FIRApp* default_app = [FIRApp defaultApp]; + default_app.getUIDImplementation = ^NSString* { + return @"I'm a fake uid."; + }; + app_ready_ = true; + } + + bool app_ready_; +}; + +// Set kPlist above before enable. +TEST_F(FirebaseCredentialsProviderTest, GetToken) { + if (!app_ready_) { + return; + } + + FirebaseCredentialsProvider credentials_provider([FIRApp defaultApp]); + credentials_provider.GetToken( + /*force_refresh=*/true, + [](const Token& token, const absl::string_view error) { + EXPECT_EQ("", token.token()); + const User& user = token.user(); + EXPECT_EQ("I'm a fake uid.", user.uid()); + EXPECT_TRUE(user.is_authenticated()); + EXPECT_EQ("", error) << error; + }); +} + +// Set kPlist above before enable. +TEST_F(FirebaseCredentialsProviderTest, SetListener) { + if (!app_ready_) { + return; + } + + FirebaseCredentialsProvider credentials_provider([FIRApp defaultApp]); + credentials_provider.SetUserChangeListener([](const User& user) { + EXPECT_EQ("I'm a fake uid.", user.uid()); + EXPECT_TRUE(user.is_authenticated()); + }); + + // TODO(wilhuff): We should wait for the above expectations to actually happen + // before continuing. + + credentials_provider.SetUserChangeListener(nullptr); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/auth/token_test.cc b/Firestore/core/test/firebase/firestore/auth/token_test.cc new file mode 100644 index 0000000..a0f2c48 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/token_test.cc @@ -0,0 +1,33 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/token.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +TEST(Token, Getter) { + Token token("token", User("abc")); + EXPECT_EQ("token", token.token()); + EXPECT_EQ(User("abc"), token.user()); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/auth/user_test.cc b/Firestore/core/test/firebase/firestore/auth/user_test.cc new file mode 100644 index 0000000..a9f764d --- /dev/null +++ b/Firestore/core/test/firebase/firestore/auth/user_test.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2018 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. + */ + +#include "Firestore/core/src/firebase/firestore/auth/user.h" + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace auth { + +TEST(User, Getter) { + User anonymous; + EXPECT_EQ("", anonymous.uid()); + EXPECT_FALSE(anonymous.is_authenticated()); + + User signin("abc"); + EXPECT_EQ("abc", signin.uid()); + EXPECT_TRUE(signin.is_authenticated()); + + User copy; + copy = signin; + EXPECT_EQ(signin, copy); +} + +TEST(User, Unauthenticated) { + User unauthenticated = User::Unauthenticated(); + EXPECT_EQ("", unauthenticated.uid()); + EXPECT_FALSE(unauthenticated.is_authenticated()); +} + +TEST(User, Comparison) { + EXPECT_EQ(User(), User()); + EXPECT_EQ(User("abc"), User("abc")); + EXPECT_NE(User(), User("abc")); + EXPECT_NE(User("abc"), User("xyz")); +} + +} // namespace auth +} // namespace firestore +} // namespace firebase |