Commit 9de4b0be authored by Tanja Gornak's avatar Tanja Gornak Committed by Commit Bot

Move Oauth token routine to the registraion manager.

Bug: 867334, 801985
Change-Id: I306bb35f1788acdfd83b772c428fd718ee13b61a
Reviewed-on: https://chromium-review.googlesource.com/1154983
Commit-Queue: Tatiana Gornak <melandory@chromium.org>
Reviewed-by: default avatarPavel Yatsuk <pavely@chromium.org>
Cr-Commit-Position: refs/heads/master@{#584387}
parent 270adc66
......@@ -11,35 +11,7 @@
#include "components/invalidation/public/invalidation_util.h"
#include "components/invalidation/public/invalidator_state.h"
#include "components/invalidation/public/object_id_invalidation_map.h"
const char kFCMOAuthScope[] =
"https://www.googleapis.com/auth/firebase.messaging";
static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
0,
// Initial delay for exponential back-off in ms.
2000,
// Factor by which the waiting time will be multiplied.
2,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0.2, // 20%
// Maximum amount of time we are willing to delay our request in ms.
1000 * 3600 * 4, // 4 hours.
// Time to keep an entry from being discarded even when it
// has no significant state, -1 to never discard.
-1,
// Don't use initial delay unless the last request was an error.
false,
};
#include "google_apis/gaia/gaia_constants.h"
namespace invalidation {
......@@ -50,10 +22,9 @@ FCMInvalidationService::FCMInvalidationService(
PrefService* pref_service,
const syncer::ParseJSONCallback& parse_json,
network::mojom::URLLoaderFactory* loader_factory)
: identity_provider_(std::move(identity_provider)),
request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy),
gcm_driver_(gcm_driver),
: gcm_driver_(gcm_driver),
instance_id_driver_(instance_id_driver),
identity_provider_(std::move(identity_provider)),
pref_service_(pref_service),
parse_json_(parse_json),
loader_factory_(loader_factory) {}
......@@ -134,7 +105,6 @@ syncer::InvalidatorState FCMInvalidationService::GetInvalidatorState() const {
}
std::string FCMInvalidationService::GetInvalidatorClientId() const {
NOTREACHED();
return std::string();
}
......@@ -150,64 +120,6 @@ void FCMInvalidationService::RequestDetailedStatus(
}
}
void FCMInvalidationService::RequestAccessToken() {
// Only one active request at a time.
if (access_token_fetcher_ != nullptr)
return;
request_access_token_retry_timer_.Stop();
OAuth2TokenService::ScopeSet oauth2_scopes = {kFCMOAuthScope};
// Invalidate previous token, otherwise the identity provider will return the
// same token again.
identity_provider_->InvalidateAccessToken(oauth2_scopes, access_token_);
access_token_.clear();
access_token_fetcher_ = identity_provider_->FetchAccessToken(
"fcm_invalidation", oauth2_scopes,
base::BindOnce(&FCMInvalidationService::OnAccessTokenRequestCompleted,
base::Unretained(this)));
}
void FCMInvalidationService::OnAccessTokenRequestCompleted(
GoogleServiceAuthError error,
std::string access_token) {
access_token_fetcher_.reset();
if (error.state() == GoogleServiceAuthError::NONE)
OnAccessTokenRequestSucceeded(access_token);
else
OnAccessTokenRequestFailed(error);
}
void FCMInvalidationService::OnAccessTokenRequestSucceeded(
std::string access_token) {
// Reset backoff time after successful response.
request_access_token_backoff_.Reset();
access_token_ = access_token;
if (!IsStarted() && IsReadyToStart()) {
StartInvalidator();
} else {
UpdateInvalidatorCredentials();
}
}
void FCMInvalidationService::OnAccessTokenRequestFailed(
GoogleServiceAuthError error) {
DCHECK_NE(error.state(), GoogleServiceAuthError::NONE);
switch (error.state()) {
case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: {
invalidator_registrar_.UpdateInvalidatorState(
syncer::INVALIDATION_CREDENTIALS_REJECTED);
break;
}
default: {
request_access_token_backoff_.InformOfRequest(false);
request_access_token_retry_timer_.Start(
FROM_HERE, request_access_token_backoff_.GetTimeUntilRelease(),
base::BindRepeating(&FCMInvalidationService::RequestAccessToken,
base::Unretained(this)));
break;
}
}
}
void FCMInvalidationService::OnActiveAccountLogin() {
if (!IsStarted() && IsReadyToStart())
StartInvalidator();
......@@ -216,20 +128,9 @@ void FCMInvalidationService::OnActiveAccountLogin() {
void FCMInvalidationService::OnActiveAccountRefreshTokenUpdated() {
if (!IsStarted() && IsReadyToStart())
StartInvalidator();
else
UpdateInvalidatorCredentials();
}
void FCMInvalidationService::OnActiveAccountRefreshTokenRemoved() {
access_token_.clear();
if (IsStarted())
UpdateInvalidatorCredentials();
}
void FCMInvalidationService::OnActiveAccountLogout() {
access_token_fetcher_.reset();
request_access_token_retry_timer_.Stop();
if (IsStarted()) {
StopInvalidator();
}
......@@ -271,36 +172,18 @@ void FCMInvalidationService::StartInvalidator() {
DCHECK(!invalidator_);
DCHECK(IsReadyToStart());
// access token before sending message to server.
if (access_token_.empty()) {
// TODO(melandory): move logic for recieving the token directly to
// the PerUserTopicRegistrationmanager.
DVLOG(1) << "FCMInvalidationService: "
<< "Deferring start until we have an access token.";
RequestAccessToken();
return;
}
auto network = std::make_unique<syncer::FCMNetworkHandler>(
gcm_driver_, instance_id_driver_);
network->StartListening();
invalidator_ = std::make_unique<syncer::FCMInvalidator>(
std::move(network), pref_service_, loader_factory_, parse_json_);
std::move(network), identity_provider_.get(), pref_service_,
loader_factory_, parse_json_);
invalidator_->RegisterHandler(this);
UpdateInvalidatorCredentials();
CHECK(invalidator_->UpdateRegisteredIds(
this, invalidator_registrar_.GetAllRegisteredIds()));
}
void FCMInvalidationService::UpdateInvalidatorCredentials() {
std::string email = identity_provider_->GetActiveAccountId();
DCHECK(!email.empty()) << "Expected user to be signed in.";
invalidator_->UpdateCredentials(email, access_token_);
}
void FCMInvalidationService::StopInvalidator() {
DCHECK(invalidator_);
// TODO(melandory): reset the network.
......
......@@ -63,16 +63,8 @@ class FCMInvalidationService : public InvalidationService,
base::RepeatingCallback<void(const base::DictionaryValue&)> caller)
const override;
void RequestAccessToken();
void OnAccessTokenRequestCompleted(GoogleServiceAuthError error,
std::string access_token);
void OnAccessTokenRequestSucceeded(std::string access_token);
void OnAccessTokenRequestFailed(GoogleServiceAuthError error);
// IdentityProvider::Observer implementation.
void OnActiveAccountRefreshTokenUpdated() override;
void OnActiveAccountRefreshTokenRemoved() override;
void OnActiveAccountLogin() override;
void OnActiveAccountLogout() override;
......@@ -94,22 +86,10 @@ class FCMInvalidationService : public InvalidationService,
void StartInvalidator();
void StopInvalidator();
void UpdateInvalidatorCredentials();
std::unique_ptr<IdentityProvider> identity_provider_;
// FCMInvalidationService needs to hold reference to access_token_fetcher_
// for the duration of request in order to receive callbacks.
std::unique_ptr<ActiveAccountAccessTokenFetcher> access_token_fetcher_;
base::OneShotTimer request_access_token_retry_timer_;
net::BackoffEntry request_access_token_backoff_;
syncer::InvalidatorRegistrar invalidator_registrar_;
std::unique_ptr<syncer::Invalidator> invalidator_;
// FCMInvalidationService needs to remember access token in order to
// invalidate it with IdentityProvider.
std::string access_token_;
// The invalidation logger object we use to record state changes
// and invalidations.
InvalidationLogger logger_;
......@@ -117,6 +97,7 @@ class FCMInvalidationService : public InvalidationService,
gcm::GCMDriver* gcm_driver_;
instance_id::InstanceIDDriver* instance_id_driver_;
std::unique_ptr<IdentityProvider> identity_provider_;
PrefService* pref_service_;
syncer::ParseJSONCallback parse_json_;
network::mojom::URLLoaderFactory* loader_factory_;
......
......@@ -9,19 +9,24 @@
#include "base/macros.h"
#include "components/invalidation/impl/fcm_sync_network_channel.h"
#include "components/invalidation/impl/per_user_topic_invalidation_client.h"
#include "components/invalidation/public/identity_provider.h"
#include "components/invalidation/public/object_id_invalidation_map.h"
namespace syncer {
FCMInvalidator::FCMInvalidator(
std::unique_ptr<FCMSyncNetworkChannel> network_channel,
invalidation::IdentityProvider* identity_provider,
PrefService* pref_service,
network::mojom::URLLoaderFactory* loader_factory,
const ParseJSONCallback& parse_json)
: pref_service_(pref_service),
loader_factory_(loader_factory),
parse_json_(parse_json),
invalidation_listener_(std::move(network_channel)) {}
: invalidation_listener_(std::move(network_channel)) {
auto registration_manager = std::make_unique<PerUserTopicRegistrationManager>(
identity_provider, pref_service, loader_factory, parse_json);
invalidation_listener_.Start(
base::BindOnce(&CreatePerUserTopicInvalidationClient), this,
std::move(registration_manager));
}
FCMInvalidator::~FCMInvalidator() {}
......@@ -38,6 +43,11 @@ bool FCMInvalidator::UpdateRegisteredIds(InvalidationHandler* handler,
return true;
}
void FCMInvalidator::UpdateCredentials(const std::string& email,
const std::string& token) {
// TODO(melandory): remove during cleanup.
}
void FCMInvalidator::UnregisterHandler(InvalidationHandler* handler) {
registrar_.UnregisterHandler(handler);
}
......@@ -46,20 +56,6 @@ InvalidatorState FCMInvalidator::GetInvalidatorState() const {
return registrar_.GetInvalidatorState();
}
void FCMInvalidator::UpdateCredentials(const std::string& email,
const std::string& token) {
if (!is_started_) {
auto registration_manager =
std::make_unique<PerUserTopicRegistrationManager>(
token, pref_service_, loader_factory_, parse_json_);
invalidation_listener_.Start(
base::BindOnce(&CreatePerUserTopicInvalidationClient), this,
std::move(registration_manager));
is_started_ = true;
}
// TODO(melandory): The token change is irrelevant for current implementation.
}
void FCMInvalidator::OnInvalidate(
const ObjectIdInvalidationMap& invalidation_map) {
registrar_.DispatchInvalidationsToHandlers(invalidation_map);
......
......@@ -15,6 +15,10 @@
class PrefService;
namespace invalidation {
class IdentityProvider;
}
namespace syncer {
class FCMSyncNetworkChannel;
......@@ -25,6 +29,7 @@ class FCMInvalidator : public Invalidator,
public FCMSyncInvalidationListener::Delegate {
public:
FCMInvalidator(std::unique_ptr<FCMSyncNetworkChannel> network_channel,
invalidation::IdentityProvider* identity_provider,
PrefService* pref_service,
network::mojom::URLLoaderFactory* loader_factory,
const ParseJSONCallback& parse_json);
......@@ -53,12 +58,6 @@ class FCMInvalidator : public Invalidator,
bool is_started_ = false;
InvalidatorRegistrar registrar_;
// Needed for the creation of the registration manager.
std::string instance_id_token_;
PrefService* pref_service_;
network::mojom::URLLoaderFactory* loader_factory_ = nullptr;
syncer::ParseJSONCallback parse_json_;
// The invalidation listener.
FCMSyncInvalidationListener invalidation_listener_;
......
......@@ -14,9 +14,11 @@
#include "components/invalidation/impl/invalidator_test_template.h"
#include "components/invalidation/impl/json_unsafe_parser.h"
#include "components/invalidation/impl/per_user_topic_registration_manager.h"
#include "components/invalidation/impl/profile_identity_provider.h"
#include "components/invalidation/impl/push_client_channel.h"
#include "components/prefs/testing_pref_service.h"
#include "net/url_request/url_request_test_util.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -38,8 +40,12 @@ class FCMInvalidatorTestDelegate {
const base::WeakPtr<InvalidationStateTracker>&) {
DCHECK(!invalidator_);
auto network_channel = std::make_unique<FCMSyncNetworkChannel>();
identity_provider_ =
std::make_unique<invalidation::ProfileIdentityProvider>(
identity_test_env_.identity_manager());
invalidator_.reset(new FCMInvalidator(
std::move(network_channel), &pref_service_, &url_loader_factory_,
std::move(network_channel), identity_provider_.get(), &pref_service_,
&url_loader_factory_,
base::BindRepeating(&syncer::JsonUnsafeParser::Parse)));
}
......@@ -64,6 +70,8 @@ class FCMInvalidatorTestDelegate {
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<FCMInvalidator> invalidator_;
identity::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<invalidation::IdentityProvider> identity_provider_;
network::TestURLLoaderFactory url_loader_factory_;
TestingPrefServiceSimple pref_service_;
};
......
......@@ -84,7 +84,7 @@ void FCMSyncInvalidationListener::Start(
void FCMSyncInvalidationListener::UpdateRegisteredIds(const ObjectIdSet& ids) {
registered_ids_ = ConvertToInvalidationObjectIdSet(ids);
if (ticl_state_ == INVALIDATIONS_ENABLED &&
per_user_topic_registration_manager_)
per_user_topic_registration_manager_ && !token_.empty())
DoRegistrationUpdate();
}
......@@ -191,6 +191,7 @@ void FCMSyncInvalidationListener::InformTokenRecieved(
const std::string& token) {
DCHECK_EQ(client, invalidation_client_.get());
token_ = token;
DoRegistrationUpdate();
}
void FCMSyncInvalidationListener::Acknowledge(const invalidation::ObjectId& id,
......
......@@ -183,7 +183,7 @@ class MockRegistrationManager : public PerUserTopicRegistrationManager {
public:
MockRegistrationManager()
: PerUserTopicRegistrationManager(
"fake_access_token",
nullptr /* identity_provider */,
nullptr /* pref_service */,
nullptr /* loader_factory */,
base::BindRepeating(&syncer::JsonUnsafeParser::Parse)) {}
......@@ -308,6 +308,7 @@ class FCMSyncInvalidationListenerTest : public testing::Test {
void EnableNotifications() {
fcm_sync_network_channel_->NotifyChannelStateChange(INVALIDATIONS_ENABLED);
listener_.InformTokenRecieved(fake_invalidation_client_, "token");
}
void DisableNotifications(InvalidatorState state) {
......
......@@ -17,6 +17,7 @@
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/invalidation/public/identity_provider.h"
#include "components/invalidation/public/invalidation_object_id.h"
#include "components/invalidation/public/invalidation_util.h"
#include "components/prefs/pref_registry_simple.h"
......@@ -35,6 +36,35 @@ const char kInvalidationRegistrationScope[] =
const char kProjectId[] = "8181035976";
const char kFCMOAuthScope[] =
"https://www.googleapis.com/auth/firebase.messaging";
static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
0,
// Initial delay for exponential back-off in ms.
2000,
// Factor by which the waiting time will be multiplied.
2,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0.2, // 20%
// Maximum amount of time we are willing to delay our request in ms.
1000 * 3600 * 4, // 4 hours.
// Time to keep an entry from being discarded even when it
// has no significant state, -1 to never discard.
-1,
// Don't use initial delay unless the last request was an error.
false,
};
} // namespace
// static
......@@ -106,12 +136,13 @@ void PerUserTopicRegistrationManager::RegistrationEntry::RegistrationFinished(
}
PerUserTopicRegistrationManager::PerUserTopicRegistrationManager(
const std::string& access_token,
invalidation::IdentityProvider* identity_provider,
PrefService* local_state,
network::mojom::URLLoaderFactory* url_loader_factory,
const ParseJSONCallback& parse_json)
: local_state_(local_state),
access_token_(access_token),
identity_provider_(identity_provider),
request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy),
parse_json_(parse_json),
url_loader_factory_(url_loader_factory) {}
......@@ -129,18 +160,23 @@ void PerUserTopicRegistrationManager::UpdateRegisteredIds(
registration_statuses_[objectId] =
std::make_unique<RegistrationEntry>(objectId, local_state_);
}
if (registration_statuses_[objectId]->state ==
RegistrationEntry::UNREGISTERED) {
TryToRegisterId(objectId);
}
RequestAccessToken();
}
void PerUserTopicRegistrationManager::DoRegistrationUpdate() {
for (const auto& registration_status : registration_statuses_) {
if (registration_status.second->state == RegistrationEntry::UNREGISTERED) {
StartRegistrationRequest(registration_status.first);
}
}
}
void PerUserTopicRegistrationManager::TryToRegisterId(
void PerUserTopicRegistrationManager::StartRegistrationRequest(
const invalidation::InvalidationObjectId& id) {
auto it = registration_statuses_.find(id);
if (it == registration_statuses_.end()) {
NOTREACHED() << "TryToRegisterId called on "
NOTREACHED() << "StartRegistrationRequest called on "
<< InvalidationObjectIdToString(id)
<< " which is not in the registration map";
return;
......@@ -174,4 +210,55 @@ InvalidationObjectIdSet PerUserTopicRegistrationManager::GetRegisteredIds()
return ids;
}
void PerUserTopicRegistrationManager::RequestAccessToken() {
// TODO(melandory): Implement traffic optimisation.
// * Before sending request to server ask for access token from identity
// provider (don't invalidate previous token).
// Identity provider will take care of retrieving/caching.
// * Only invalidate access token when server didn't accept it.
// Only one active request at a time.
if (access_token_fetcher_ != nullptr)
return;
request_access_token_retry_timer_.Stop();
OAuth2TokenService::ScopeSet oauth2_scopes = {kFCMOAuthScope};
// Invalidate previous token, otherwise the identity provider will return the
// same token again.
identity_provider_->InvalidateAccessToken(oauth2_scopes, access_token_);
access_token_.clear();
access_token_fetcher_ = identity_provider_->FetchAccessToken(
"fcm_invalidation", oauth2_scopes,
base::BindOnce(
&PerUserTopicRegistrationManager::OnAccessTokenRequestCompleted,
base::Unretained(this)));
}
void PerUserTopicRegistrationManager::OnAccessTokenRequestCompleted(
GoogleServiceAuthError error,
std::string access_token) {
access_token_fetcher_.reset();
if (error.state() == GoogleServiceAuthError::NONE)
OnAccessTokenRequestSucceeded(access_token);
else
OnAccessTokenRequestFailed(error);
}
void PerUserTopicRegistrationManager::OnAccessTokenRequestSucceeded(
std::string access_token) {
// Reset backoff time after successful response.
request_access_token_backoff_.Reset();
access_token_ = access_token;
DoRegistrationUpdate();
}
void PerUserTopicRegistrationManager::OnAccessTokenRequestFailed(
GoogleServiceAuthError error) {
DCHECK_NE(error.state(), GoogleServiceAuthError::NONE);
request_access_token_backoff_.InformOfRequest(false);
request_access_token_retry_timer_.Start(
FROM_HERE, request_access_token_backoff_.GetTimeUntilRelease(),
base::BindRepeating(&PerUserTopicRegistrationManager::RequestAccessToken,
base::Unretained(this)));
}
} // namespace syncer
......@@ -13,14 +13,21 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/invalidation/impl/per_user_topic_registration_request.h"
#include "components/invalidation/public/identity_provider.h"
#include "components/invalidation/public/invalidation_export.h"
#include "components/invalidation/public/invalidation_object_id.h"
#include "components/invalidation/public/invalidation_util.h"
#include "net/base/backoff_entry.h"
#include "net/url_request/url_request_context_getter.h"
class PrefRegistrySimple;
class PrefService;
namespace invalidation {
class ActiveAccountAccessTokenFetcher;
class IdentityProvider;
} // namespace invalidation
namespace syncer {
// A class that manages the registration of types for server-issued
......@@ -35,7 +42,7 @@ namespace syncer {
class INVALIDATION_EXPORT PerUserTopicRegistrationManager {
public:
PerUserTopicRegistrationManager(
const std::string& access_token,
invalidation::IdentityProvider* identity_provider,
PrefService* local_state,
network::mojom::URLLoaderFactory* url_loader_factory,
const ParseJSONCallback& parse_json);
......@@ -52,24 +59,37 @@ class INVALIDATION_EXPORT PerUserTopicRegistrationManager {
private:
struct RegistrationEntry;
void DoRegistrationUpdate();
// Tries to register |id|. No retry in case of failure.
void TryToRegisterId(const invalidation::InvalidationObjectId& id);
void StartRegistrationRequest(const invalidation::InvalidationObjectId& id);
// Unregisters the given object ID.
void UnregisterId(const invalidation::InvalidationObjectId& id);
void RequestAccessToken();
void OnAccessTokenRequestCompleted(GoogleServiceAuthError error,
std::string access_token);
void OnAccessTokenRequestSucceeded(std::string access_token);
void OnAccessTokenRequestFailed(GoogleServiceAuthError error);
std::map<invalidation::InvalidationObjectId,
std::unique_ptr<RegistrationEntry>,
InvalidationObjectIdLessThan>
registration_statuses_;
// Token derrived from GCM IID.
std::string token_;
PrefService* local_state_ = nullptr;
// OAuth Header.
invalidation::IdentityProvider* const identity_provider_;
std::string access_token_;
// Token derrived from GCM IID.
std::string token_;
std::unique_ptr<invalidation::ActiveAccountAccessTokenFetcher>
access_token_fetcher_;
base::OneShotTimer request_access_token_retry_timer_;
net::BackoffEntry request_access_token_backoff_;
// The callback for Parsing JSON.
ParseJSONCallback parse_json_;
......
......@@ -9,9 +9,11 @@
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "components/invalidation/impl/json_unsafe_parser.h"
#include "components/invalidation/impl/profile_identity_provider.h"
#include "components/invalidation/public/invalidation_util.h"
#include "components/prefs/testing_pref_service.h"
#include "net/http/http_status_code.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
......@@ -82,11 +84,16 @@ class PerUserTopicRegistrationManagerTest : public testing::Test {
void SetUp() override {
PerUserTopicRegistrationManager::RegisterProfilePrefs(
pref_service_.registry());
identity_test_env_.MakePrimaryAccountAvailable("example@gmail.com");
identity_test_env_.SetAutomaticIssueOfAccessTokens(true);
identity_provider_ =
std::make_unique<invalidation::ProfileIdentityProvider>(
identity_test_env_.identity_manager());
}
std::unique_ptr<PerUserTopicRegistrationManager> BuildRegistrationManager() {
return std::make_unique<PerUserTopicRegistrationManager>(
"access_token", &pref_service_, url_loader_factory(),
identity_provider_.get(), &pref_service_, url_loader_factory(),
base::BindRepeating(&syncer::JsonUnsafeParser::Parse));
}
......@@ -101,6 +108,9 @@ class PerUserTopicRegistrationManagerTest : public testing::Test {
network::TestURLLoaderFactory url_loader_factory_;
TestingPrefServiceSimple pref_service_;
identity::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<invalidation::IdentityProvider> identity_provider_;
DISALLOW_COPY_AND_ASSIGN(PerUserTopicRegistrationManagerTest);
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment