Commit c2afb9b8 authored by James Cook's avatar James Cook Committed by Commit Bot

chromeos: Migrate DriveFS off the mojo Identity Service

The code in //chromeos/components/drivefs runs in the browser process
on the UI thread. There aren't any plans to move it out of process.
It can directly use the C++ IdentityManager instead of using the mojo
Identity Service. This will eliminate the last client of the Identity
Service, making it easier to refactor or delete it.

Convert DriveFsAuth to use PrimaryAccountAccessTokenFetcher, which
automatically handles waiting for / getting information about the
primary account.

Migrate the tests to use IdentityTestEnvironment, specifically the
helpers for access token requests. This requires rewriting tests
that used GMock on the Identity Service mojo API, but ends up being
less code overall.

Test: rewrite the chromeos_components_unittests
Test: Google Drive still works in File Manager to read / write / copy
      files.
Bug: 1054673

Change-Id: If83be823ee2ab1936289e21b2c50451004557c17
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2063419
Commit-Queue: James Cook <jamescook@chromium.org>
Reviewed-by: default avatarAustin Tankiang <austinct@chromium.org>
Reviewed-by: default avatarColin Blundell <blundell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#744097}
parent 60e93eb9
......@@ -48,6 +48,7 @@
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/user_manager/user.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_context.h"
......@@ -60,7 +61,6 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "services/identity/public/mojom/identity_service.mojom.h"
#include "services/network/public/cpp/network_connection_tracker.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/service_manager/public/cpp/connector.h"
......@@ -456,12 +456,8 @@ class DriveIntegrationService::DriveFsHolder
return profile_->GetURLLoaderFactory();
}
void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver)
override {
auto* service = profile_->GetIdentityService();
if (service)
service->BindIdentityAccessor(std::move(receiver));
signin::IdentityManager* GetIdentityManager() override {
return IdentityManagerFactory::GetForProfile(profile_);
}
const AccountId& GetAccountId() override {
......
......@@ -30,12 +30,12 @@ component("drivefs") {
"//chromeos/disks",
"//components/account_id",
"//components/drive",
"//components/signin/public/identity_manager",
"//dbus",
"//google_apis",
"//mojo/public/cpp/bindings",
"//mojo/public/cpp/platform",
"//net",
"//services/identity/public/mojom",
"//services/network/public/cpp:cpp",
]
defines = [ "IS_DRIVEFS_IMPL" ]
......@@ -77,10 +77,11 @@ source_set("unit_tests") {
"//components/account_id",
"//components/drive",
"//components/invalidation/impl:test_support",
"//components/signin/public/identity_manager",
"//components/signin/public/identity_manager:test_support",
"//mojo/public/cpp/bindings",
"//net",
"//net:test_support",
"//services/identity/public/mojom",
"//services/network:test_support",
"//services/network/public/cpp:cpp",
"//testing/gmock",
......
include_rules = [
"+components/drive",
"+components/invalidation/impl/fake_invalidation_service.h",
"+components/signin",
"+mojo/public",
"+services/identity/public",
]
......@@ -6,6 +6,11 @@
#include "base/bind.h"
#include "components/account_id/account_id.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace drivefs {
......@@ -23,7 +28,7 @@ DriveFsAuth::DriveFsAuth(const base::Clock* clock,
timer_(std::move(timer)),
delegate_(delegate) {}
DriveFsAuth::~DriveFsAuth() {}
DriveFsAuth::~DriveFsAuth() = default;
base::Optional<std::string> DriveFsAuth::GetCachedAccessToken() {
const auto& token = GetOrResetCachedToken(true);
......@@ -48,35 +53,32 @@ void DriveFsAuth::GetAccessToken(
return;
}
signin::IdentityManager* identity_manager = delegate_->GetIdentityManager();
if (!identity_manager) {
std::move(callback).Run(mojom::AccessTokenStatus::kAuthError, "");
return;
}
get_access_token_callback_ = std::move(callback);
timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(30),
base::BindOnce(&DriveFsAuth::AuthTimeout,
weak_ptr_factory_.GetWeakPtr()));
GetIdentityAccessor()->GetUnconsentedPrimaryAccountWhenAvailable(
base::BindOnce(&DriveFsAuth::AccountReady,
weak_ptr_factory_.GetWeakPtr()));
}
void DriveFsAuth::AccountReady(const CoreAccountId& account_id,
const std::string& gaia,
const std::string& email,
const identity::AccountState& state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
weak_ptr_factory_.InvalidateWeakPtrs();
timer_->Stop();
GetIdentityAccessor()->GetAccessToken(
account_id, {"https://www.googleapis.com/auth/drive"},
kIdentityConsumerId,
// Timer is cancelled when it is destroyed, so use base::Unretained().
timer_->Start(
FROM_HERE, base::TimeDelta::FromSeconds(30),
base::BindOnce(&DriveFsAuth::AuthTimeout, base::Unretained(this)));
std::set<std::string> scopes({"https://www.googleapis.com/auth/drive"});
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
kIdentityConsumerId, identity_manager, scopes,
base::BindOnce(&DriveFsAuth::GotChromeAccessToken,
base::Unretained(this)));
base::Unretained(this)),
signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable,
signin::ConsentLevel::kNotRequired);
}
void DriveFsAuth::GotChromeAccessToken(
const base::Optional<std::string>& access_token,
base::Time expiration_time,
const GoogleServiceAuthError& error) {
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!access_token) {
timer_->Stop();
if (error.state() != GoogleServiceAuthError::NONE) {
std::move(get_access_token_callback_)
.Run(error.IsPersistentError()
? mojom::AccessTokenStatus::kAuthError
......@@ -84,9 +86,9 @@ void DriveFsAuth::GotChromeAccessToken(
"");
return;
}
UpdateCachedToken(*access_token, expiration_time);
UpdateCachedToken(access_token_info.token, access_token_info.expiration_time);
std::move(get_access_token_callback_)
.Run(mojom::AccessTokenStatus::kSuccess, *access_token);
.Run(mojom::AccessTokenStatus::kSuccess, access_token_info.token);
}
const std::string& DriveFsAuth::GetOrResetCachedToken(bool use_cached) {
......@@ -104,18 +106,8 @@ void DriveFsAuth::UpdateCachedToken(const std::string& token,
void DriveFsAuth::AuthTimeout() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
weak_ptr_factory_.InvalidateWeakPtrs();
std::move(get_access_token_callback_)
.Run(mojom::AccessTokenStatus::kAuthError, "");
}
identity::mojom::IdentityAccessor* DriveFsAuth::GetIdentityAccessor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!identity_accessor_) {
delegate_->BindIdentityAccessor(
identity_accessor_.BindNewPipeAndPassReceiver());
}
return identity_accessor_.get();
}
} // namespace drivefs
......@@ -11,20 +11,23 @@
#include "base/component_export.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/time/clock.h"
#include "base/timer/timer.h"
#include "chromeos/components/drivefs/mojom/drivefs.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/identity/public/mojom/identity_accessor.mojom.h"
class AccountId;
class GoogleServiceAuthError;
namespace network {
class SharedURLLoaderFactory;
} // namespace network
namespace signin {
struct AccessTokenInfo;
class IdentityManager;
class PrimaryAccountAccessTokenFetcher;
} // namespace signin
namespace drivefs {
class COMPONENT_EXPORT(DRIVEFS) DriveFsAuth {
......@@ -36,8 +39,7 @@ class COMPONENT_EXPORT(DRIVEFS) DriveFsAuth {
virtual scoped_refptr<network::SharedURLLoaderFactory>
GetURLLoaderFactory() = 0;
virtual void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver) = 0;
virtual signin::IdentityManager* GetIdentityManager() = 0;
virtual const AccountId& GetAccountId() = 0;
virtual std::string GetObfuscatedAccountId() = 0;
virtual bool IsMetricsCollectionEnabled() = 0;
......@@ -71,14 +73,8 @@ class COMPONENT_EXPORT(DRIVEFS) DriveFsAuth {
mojom::DriveFsDelegate::GetAccessTokenCallback callback);
private:
void AccountReady(const CoreAccountId& account_id,
const std::string& gaia,
const std::string& email,
const identity::AccountState& state);
void GotChromeAccessToken(const base::Optional<std::string>& access_token,
base::Time expiration_time,
const GoogleServiceAuthError& error);
void GotChromeAccessToken(GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
const std::string& GetOrResetCachedToken(bool use_cached);
......@@ -86,16 +82,14 @@ class COMPONENT_EXPORT(DRIVEFS) DriveFsAuth {
void AuthTimeout();
identity::mojom::IdentityAccessor* GetIdentityAccessor();
SEQUENCE_CHECKER(sequence_checker_);
const base::Clock* const clock_;
const base::FilePath profile_path_;
const std::unique_ptr<base::OneShotTimer> timer_;
Delegate* const delegate_;
// The connection to the identity service. Access via |GetIdentityAccessor()|.
mojo::Remote<identity::mojom::IdentityAccessor> identity_accessor_;
std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
access_token_fetcher_;
// Pending callback for an in-flight GetAccessToken request.
mojom::DriveFsDelegate::GetAccessTokenCallback get_access_token_callback_;
......@@ -103,7 +97,6 @@ class COMPONENT_EXPORT(DRIVEFS) DriveFsAuth {
std::string last_token_;
base::Time last_token_expiry_;
base::WeakPtrFactory<DriveFsAuth> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(DriveFsAuth);
};
......
......@@ -5,6 +5,7 @@
#include "chromeos/components/drivefs/drivefs_auth.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
......@@ -13,9 +14,10 @@
#include "base/test/task_environment.h"
#include "base/timer/mock_timer.h"
#include "components/account_id/account_id.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "services/identity/public/mojom/identity_accessor.mojom-test-utils.h"
#include "services/identity/public/mojom/identity_service.mojom.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -25,11 +27,14 @@ namespace {
using testing::_;
constexpr char kTestEmail[] = "test@example.com";
constexpr base::TimeDelta kTokenLifetime = base::TimeDelta::FromHours(1);
class AuthDelegateImpl : public DriveFsAuth::Delegate {
public:
AuthDelegateImpl(identity::mojom::IdentityService* identity_service,
AuthDelegateImpl(signin::IdentityManager* identity_manager,
const AccountId& account_id)
: identity_service_(identity_service), account_id_(account_id) {}
: identity_manager_(identity_manager), account_id_(account_id) {}
~AuthDelegateImpl() override = default;
......@@ -39,10 +44,8 @@ class AuthDelegateImpl : public DriveFsAuth::Delegate {
override {
return nullptr;
}
void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver)
override {
identity_service_->BindIdentityAccessor(std::move(receiver));
signin::IdentityManager* GetIdentityManager() override {
return identity_manager_;
}
const AccountId& GetAccountId() override { return account_id_; }
std::string GetObfuscatedAccountId() override {
......@@ -51,95 +54,25 @@ class AuthDelegateImpl : public DriveFsAuth::Delegate {
bool IsMetricsCollectionEnabled() override { return false; }
identity::mojom::IdentityService* const identity_service_;
signin::IdentityManager* const identity_manager_;
const AccountId account_id_;
DISALLOW_COPY_AND_ASSIGN(AuthDelegateImpl);
};
class MockIdentityAccessor {
public:
MOCK_METHOD3(
GetAccessToken,
std::pair<base::Optional<std::string>, GoogleServiceAuthError::State>(
const CoreAccountId& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id));
mojo::ReceiverSet<identity::mojom::IdentityAccessor>* receivers_ = nullptr;
};
class FakeIdentityService
: public identity::mojom::IdentityAccessorInterceptorForTesting,
public identity::mojom::IdentityService {
public:
explicit FakeIdentityService(MockIdentityAccessor* mock,
const base::Clock* clock)
: mock_(mock), clock_(clock) {
mock_->receivers_ = &receivers_;
}
~FakeIdentityService() override { mock_->receivers_ = nullptr; }
void set_auth_enabled(bool enabled) { auth_enabled_ = enabled; }
private:
// identity::mojom::IdentityService:
void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver)
override {
receivers_.Add(this, std::move(receiver));
}
// identity::mojom::IdentityAccessorInterceptorForTesting overrides:
void GetUnconsentedPrimaryAccountWhenAvailable(
GetUnconsentedPrimaryAccountWhenAvailableCallback callback) override {
if (!auth_enabled_) {
return;
}
auto account_id = AccountId::FromUserEmailGaiaId("test@example.com", "ID");
std::move(callback).Run(CoreAccountId(account_id.GetUserEmail()),
account_id.GetGaiaId(), account_id.GetUserEmail(),
{});
}
void GetAccessToken(const CoreAccountId& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id,
GetAccessTokenCallback callback) override {
auto result = mock_->GetAccessToken(account_id, scopes, consumer_id);
std::move(callback).Run(std::move(result.first),
clock_->Now() + base::TimeDelta::FromSeconds(1),
GoogleServiceAuthError(result.second));
}
IdentityAccessor* GetForwardingInterface() override {
NOTREACHED();
return nullptr;
}
MockIdentityAccessor* const mock_;
const base::Clock* const clock_;
mojo::ReceiverSet<identity::mojom::IdentityAccessor> receivers_;
bool auth_enabled_ = true;
DISALLOW_COPY_AND_ASSIGN(FakeIdentityService);
};
class DriveFsAuthTest : public ::testing::Test {
public:
DriveFsAuthTest() : kTestAccountId("test@example.com") {}
DriveFsAuthTest() = default;
protected:
void SetUp() override {
clock_.SetNow(base::Time::Now());
identity_service_ = std::make_unique<FakeIdentityService>(
&mock_identity_accessor_, &clock_);
identity_test_env_.MakeUnconsentedPrimaryAccountAvailable(kTestEmail);
auto timer = std::make_unique<base::MockOneShotTimer>();
timer_ = timer.get();
delegate_ = std::make_unique<AuthDelegateImpl>(
identity_service_.get(),
AccountId::FromUserEmailGaiaId("test@example.com", "ID"));
identity_test_env_.identity_manager(),
AccountId::FromUserEmailGaiaId(kTestEmail, "ID"));
auth_ = std::make_unique<DriveFsAuth>(&clock_,
base::FilePath("/path/to/profile"),
std::move(timer), delegate_.get());
......@@ -150,26 +83,21 @@ class DriveFsAuthTest : public ::testing::Test {
auth_.reset();
}
void ExpectAccessToken(bool use_cached,
mojom::AccessTokenStatus expected_status,
const std::string& expected_token) {
base::RunLoop run_loop;
auto quit_closure = run_loop.QuitClosure();
auth_->GetAccessToken(use_cached, base::BindLambdaForTesting(
[&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(expected_status, status);
EXPECT_EQ(expected_token, token);
std::move(quit_closure).Run();
}));
run_loop.Run();
// Helper function for better line wrapping.
void RespondWithAccessToken(const std::string& token) {
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
token, clock_.Now() + kTokenLifetime);
}
// Helper function for better line wrapping.
void RespondWithAuthError(GoogleServiceAuthError::State error_state) {
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
GoogleServiceAuthError(error_state));
}
const CoreAccountId kTestAccountId;
base::test::TaskEnvironment task_environment_;
MockIdentityAccessor mock_identity_accessor_;
signin::IdentityTestEnvironment identity_test_env_;
base::SimpleTestClock clock_;
std::unique_ptr<FakeIdentityService> identity_service_;
std::unique_ptr<AuthDelegateImpl> delegate_;
std::unique_ptr<DriveFsAuth> auth_;
......@@ -180,31 +108,45 @@ class DriveFsAuthTest : public ::testing::Test {
};
TEST_F(DriveFsAuthTest, GetAccessToken_Success) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)));
ExpectAccessToken(false, mojom::AccessTokenStatus::kSuccess, "auth token");
base::RunLoop run_loop;
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
run_loop.Quit();
}));
RespondWithAccessToken("auth token");
run_loop.Run();
}
TEST_F(DriveFsAuthTest, GetAccessToken_GetAccessTokenFailure_Permanent) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(std::make_pair(
base::nullopt, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)));
ExpectAccessToken(false, mojom::AccessTokenStatus::kAuthError, "");
base::RunLoop run_loop;
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kAuthError, status);
EXPECT_TRUE(token.empty());
run_loop.Quit();
}));
RespondWithAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
run_loop.Run();
}
TEST_F(DriveFsAuthTest, GetAccessToken_GetAccessTokenFailure_Transient) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(std::make_pair(
base::nullopt, GoogleServiceAuthError::SERVICE_UNAVAILABLE)));
ExpectAccessToken(false, mojom::AccessTokenStatus::kTransientError, "");
base::RunLoop run_loop;
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kTransientError, status);
EXPECT_TRUE(token.empty());
run_loop.Quit();
}));
RespondWithAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
run_loop.Run();
}
TEST_F(DriveFsAuthTest, GetAccessToken_GetAccessTokenFailure_Timeout) {
identity_service_->set_auth_enabled(false);
base::RunLoop run_loop;
auto quit_closure = run_loop.QuitClosure();
auth_->GetAccessToken(
......@@ -213,16 +155,13 @@ TEST_F(DriveFsAuthTest, GetAccessToken_GetAccessTokenFailure_Timeout) {
EXPECT_EQ(mojom::AccessTokenStatus::kAuthError, status);
std::move(quit_closure).Run();
}));
// Timer fires before access token becomes available.
timer_->Fire();
run_loop.Run();
}
TEST_F(DriveFsAuthTest, GetAccessToken_ParallelRequests) {
base::RunLoop run_loop;
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)));
auto quit_closure = run_loop.QuitClosure();
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
......@@ -237,69 +176,103 @@ TEST_F(DriveFsAuthTest, GetAccessToken_ParallelRequests) {
EXPECT_EQ(mojom::AccessTokenStatus::kTransientError, status);
EXPECT_TRUE(token.empty());
}));
RespondWithAccessToken("auth token");
run_loop.Run();
}
TEST_F(DriveFsAuthTest, GetAccessToken_SequentialRequests) {
for (int i = 0; i < 3; ++i) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)));
ExpectAccessToken(false, mojom::AccessTokenStatus::kSuccess, "auth token");
base::RunLoop run_loop;
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
run_loop.Quit();
}));
RespondWithAccessToken("auth token");
run_loop.Run();
}
for (int i = 0; i < 3; ++i) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(std::make_pair(
base::nullopt, GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)));
ExpectAccessToken(false, mojom::AccessTokenStatus::kAuthError, "");
base::RunLoop run_loop;
auth_->GetAccessToken(
false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kAuthError, status);
EXPECT_TRUE(token.empty());
run_loop.Quit();
}));
RespondWithAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
run_loop.Run();
}
}
TEST_F(DriveFsAuthTest, Caching) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)));
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token");
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
RespondWithAccessToken("auth token");
// Second attempt should reuse already available token.
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token");
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
TEST_F(DriveFsAuthTest, CachedAndNotCached) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)))
.WillOnce(testing::Return(
std::make_pair("auth token 2", GoogleServiceAuthError::NONE)));
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token");
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
RespondWithAccessToken("auth token");
// Second attempt should reuse already available token.
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token");
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
// Now ask for token explicitly bypassing the cache.
ExpectAccessToken(false, mojom::AccessTokenStatus::kSuccess, "auth token 2");
auth_->GetAccessToken(
false, base::BindOnce(
[](mojom::AccessTokenStatus status, const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token 2", token);
}));
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
RespondWithAccessToken("auth token 2");
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
TEST_F(DriveFsAuthTest, CacheExpired) {
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(kTestAccountId, _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)))
.WillOnce(testing::Return(
std::make_pair("auth token 2", GoogleServiceAuthError::NONE)));
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token");
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
RespondWithAccessToken("auth token");
clock_.Advance(base::TimeDelta::FromHours(2));
// As the token expired second mount attempt go to identity.
ExpectAccessToken(true, mojom::AccessTokenStatus::kSuccess, "auth token 2");
// The token expired so a new one is requested.
auth_->GetAccessToken(true, base::BindOnce([](mojom::AccessTokenStatus status,
const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token 2", token);
}));
RespondWithAccessToken("auth token 2");
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
} // namespace
......
......@@ -28,14 +28,14 @@
#include "components/drive/drive_notification_manager.h"
#include "components/drive/drive_notification_observer.h"
#include "components/invalidation/impl/fake_invalidation_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "mojo/public/cpp/bindings/clone_traits.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/identity/public/mojom/identity_accessor.mojom-test-utils.h"
#include "services/identity/public/mojom/identity_service.mojom.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
......@@ -46,6 +46,8 @@ namespace {
using testing::_;
using MountFailure = DriveFsHost::MountObserver::MountFailure;
constexpr base::TimeDelta kTokenLifetime = base::TimeDelta::FromHours(1);
class MockDriveFs : public mojom::DriveFsInterceptorForTesting,
public mojom::SearchQuery {
public:
......@@ -95,9 +97,9 @@ class MockDriveFs : public mojom::DriveFsInterceptorForTesting,
class TestingDriveFsHostDelegate : public DriveFsHost::Delegate,
public DriveFsHost::MountObserver {
public:
TestingDriveFsHostDelegate(identity::mojom::IdentityService* identity_service,
TestingDriveFsHostDelegate(signin::IdentityManager* identity_manager,
const AccountId& account_id)
: identity_service_(identity_service),
: identity_manager_(identity_manager),
account_id_(account_id),
drive_notification_manager_(&invalidation_service_) {}
......@@ -126,10 +128,8 @@ class TestingDriveFsHostDelegate : public DriveFsHost::Delegate,
override {
return nullptr;
}
void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver)
override {
identity_service_->BindIdentityAccessor(std::move(receiver));
signin::IdentityManager* GetIdentityManager() override {
return identity_manager_;
}
const AccountId& GetAccountId() override { return account_id_; }
std::string GetObfuscatedAccountId() override {
......@@ -151,7 +151,7 @@ class TestingDriveFsHostDelegate : public DriveFsHost::Delegate,
return base::FilePath("/MyFiles");
}
identity::mojom::IdentityService* const identity_service_;
signin::IdentityManager* const identity_manager_;
const AccountId account_id_;
mojo::PendingRemote<mojom::DriveFsBootstrap> pending_bootstrap_;
invalidation::FakeInvalidationService invalidation_service_;
......@@ -160,91 +160,6 @@ class TestingDriveFsHostDelegate : public DriveFsHost::Delegate,
DISALLOW_COPY_AND_ASSIGN(TestingDriveFsHostDelegate);
};
class MockIdentityAccessor {
public:
explicit MockIdentityAccessor(const base::Clock* clock) : clock_(clock) {}
MOCK_METHOD3(
GetAccessToken,
std::pair<base::Optional<std::string>, GoogleServiceAuthError::State>(
const CoreAccountId& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id));
void OnGetAccessToken(
const CoreAccountId& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id,
identity::mojom::IdentityAccessor::GetAccessTokenCallback callback) {
if (pause_requests_) {
callbacks_.push_back(std::move(callback));
return;
}
auto result = GetAccessToken(account_id, scopes, consumer_id);
std::move(callback).Run(std::move(result.first),
clock_->Now() + base::TimeDelta::FromHours(1),
GoogleServiceAuthError(result.second));
}
std::vector<identity::mojom::IdentityAccessor::GetAccessTokenCallback>&
callbacks() {
return callbacks_;
}
void set_pause_requests(bool pause) { pause_requests_ = pause; }
const base::Clock* const clock_;
bool pause_requests_ = false;
std::vector<identity::mojom::IdentityAccessor::GetAccessTokenCallback>
callbacks_;
mojo::ReceiverSet<identity::mojom::IdentityAccessor>* receivers_ = nullptr;
};
class FakeIdentityService
: public identity::mojom::IdentityAccessorInterceptorForTesting,
public identity::mojom::IdentityService {
public:
explicit FakeIdentityService(MockIdentityAccessor* mock) : mock_(mock) {
mock_->receivers_ = &receivers_;
}
~FakeIdentityService() override { mock_->receivers_ = nullptr; }
private:
// identity::mojom::IdentityService:
void BindIdentityAccessor(
mojo::PendingReceiver<identity::mojom::IdentityAccessor> receiver)
override {
receivers_.Add(this, std::move(receiver));
}
// identity::mojom::IdentityAccessorInterceptorForTesting overrides:
void GetUnconsentedPrimaryAccountWhenAvailable(
GetUnconsentedPrimaryAccountWhenAvailableCallback callback) override {
auto account_id = AccountId::FromUserEmailGaiaId("test@example.com", "ID");
std::move(callback).Run(CoreAccountId(account_id.GetUserEmail()),
account_id.GetGaiaId(), account_id.GetUserEmail(),
{});
}
void GetAccessToken(const CoreAccountId& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id,
GetAccessTokenCallback callback) override {
mock_->OnGetAccessToken(account_id, scopes, consumer_id,
std::move(callback));
}
IdentityAccessor* GetForwardingInterface() override {
NOTREACHED();
return nullptr;
}
MockIdentityAccessor* const mock_;
mojo::ReceiverSet<identity::mojom::IdentityAccessor> receivers_;
DISALLOW_COPY_AND_ASSIGN(FakeIdentityService);
};
class MockDriveFsHostObserver : public DriveFsHostObserver {
public:
MOCK_METHOD0(OnUnmounted, void());
......@@ -262,8 +177,7 @@ class DriveFsHostTest : public ::testing::Test, public mojom::DriveFsBootstrap {
public:
DriveFsHostTest()
: network_connection_tracker_(
network::TestNetworkConnectionTracker::CreateInstance()),
mock_identity_accessor_(&clock_) {
network::TestNetworkConnectionTracker::CreateInstance()) {
clock_.SetNow(base::Time::Now());
}
......@@ -274,10 +188,10 @@ class DriveFsHostTest : public ::testing::Test, public mojom::DriveFsBootstrap {
account_id_ = AccountId::FromUserEmailGaiaId("test@example.com", "ID");
disk_manager_ = std::make_unique<chromeos::disks::MockDiskMountManager>();
identity_service_ =
std::make_unique<FakeIdentityService>(&mock_identity_accessor_);
identity_test_env_.MakeUnconsentedPrimaryAccountAvailable(
"test@example.com");
host_delegate_ = std::make_unique<TestingDriveFsHostDelegate>(
identity_service_.get(), account_id_);
identity_test_env_.identity_manager(), account_id_);
auto timer = std::make_unique<base::MockOneShotTimer>();
timer_ = timer.get();
host_ = std::make_unique<DriveFsHost>(
......@@ -377,21 +291,6 @@ class DriveFsHostTest : public ::testing::Test, public mojom::DriveFsBootstrap {
testing::Mock::VerifyAndClearExpectations(host_delegate_.get());
}
void ExpectAccessToken(mojom::AccessTokenStatus expected_status,
const std::string& expected_token) {
base::RunLoop run_loop;
auto quit_closure = run_loop.QuitClosure();
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
base::BindLambdaForTesting(
[&](mojom::AccessTokenStatus status, const std::string& token) {
EXPECT_EQ(expected_status, status);
EXPECT_EQ(expected_token, token);
std::move(quit_closure).Run();
}));
run_loop.Run();
}
void Init(mojom::DriveFsConfigurationPtr config,
mojo::PendingReceiver<mojom::DriveFs> drive_fs_receiver,
mojo::PendingRemote<mojom::DriveFsDelegate> delegate) override {
......@@ -410,8 +309,7 @@ class DriveFsHostTest : public ::testing::Test, public mojom::DriveFsBootstrap {
std::unique_ptr<network::TestNetworkConnectionTracker>
network_connection_tracker_;
base::SimpleTestClock clock_;
MockIdentityAccessor mock_identity_accessor_;
std::unique_ptr<FakeIdentityService> identity_service_;
signin::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<TestingDriveFsHostDelegate> host_delegate_;
std::unique_ptr<DriveFsHost> host_;
base::MockOneShotTimer* timer_;
......@@ -522,7 +420,7 @@ TEST_F(DriveFsHostTest, UnsupportedAccountTypes) {
};
for (auto& account : unsupported_accounts) {
host_delegate_ = std::make_unique<TestingDriveFsHostDelegate>(
identity_service_.get(), account);
identity_test_env_.identity_manager(), account);
host_ = std::make_unique<DriveFsHost>(
profile_path_, host_delegate_.get(), host_delegate_.get(),
network_connection_tracker_.get(), &clock_, disk_manager_.get(),
......@@ -535,25 +433,17 @@ TEST_F(DriveFsHostTest, UnsupportedAccountTypes) {
TEST_F(DriveFsHostTest, GetAccessToken_UnmountDuringMojoRequest) {
ASSERT_NO_FATAL_FAILURE(DoMount());
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(CoreAccountId("test@example.com"), _, "drivefs"))
.WillOnce(testing::DoAll(
testing::InvokeWithoutArgs([&]() { host_->Unmount(); }),
testing::Return(std::make_pair(
base::nullopt,
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS))));
base::RunLoop run_loop;
delegate_.set_disconnect_handler(run_loop.QuitClosure());
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
base::BindLambdaForTesting([](mojom::AccessTokenStatus status,
const std::string& token) { FAIL(); }));
host_->Unmount();
run_loop.Run();
EXPECT_FALSE(host_->IsMounted());
// Wait for the response to reach the remote if it's still open.
mock_identity_accessor_.receivers_->FlushForTesting();
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
ACTION_P(CloneStruct, output) {
......@@ -713,14 +603,21 @@ TEST_F(DriveFsHostTest, RemoveDriveNotificationObserver) {
TEST_F(DriveFsHostTest, Remount_CachedOnceOnly) {
ASSERT_NO_FATAL_FAILURE(DoMount());
EXPECT_CALL(mock_identity_accessor_,
GetAccessToken(CoreAccountId("test@example.com"), _, "drivefs"))
.WillOnce(testing::Return(
std::make_pair("auth token", GoogleServiceAuthError::NONE)))
.WillOnce(testing::Return(
std::make_pair("auth token 2", GoogleServiceAuthError::NONE)));
// Request an access token.
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
base::BindLambdaForTesting(
[&](mojom::AccessTokenStatus status, const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
delegate_.FlushForTesting();
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
ExpectAccessToken(mojom::AccessTokenStatus::kSuccess, "auth token");
// Fulfill the request.
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"auth token", clock_.Now() + kTokenLifetime);
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
base::Optional<base::TimeDelta> delay = base::TimeDelta::FromSeconds(5);
EXPECT_CALL(*host_delegate_, OnUnmounted(delay));
......@@ -730,15 +627,28 @@ TEST_F(DriveFsHostTest, Remount_CachedOnceOnly) {
// Second mount attempt should reuse already available token.
ASSERT_NO_FATAL_FAILURE(DoMount());
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
EXPECT_EQ("auth token", init_access_token_.value_or(""));
// But if it asks for token it goes straight to identity.
ExpectAccessToken(mojom::AccessTokenStatus::kSuccess, "auth token 2");
// But if it asks for token again it goes to identity manager.
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
base::BindLambdaForTesting(
[&](mojom::AccessTokenStatus status, const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token 2", token);
}));
delegate_.FlushForTesting();
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
// Fulfill the request with a different token.
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"auth token 2", clock_.Now() + kTokenLifetime);
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
TEST_F(DriveFsHostTest, Remount_RequestInflight) {
ASSERT_NO_FATAL_FAILURE(DoMount());
mock_identity_accessor_.set_pause_requests(true);
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
......@@ -750,22 +660,20 @@ TEST_F(DriveFsHostTest, Remount_RequestInflight) {
SendOnUnmounted(delay);
base::RunLoop().RunUntilIdle();
ASSERT_NO_FATAL_FAILURE(DoUnmount());
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
// Now the response is ready.
ASSERT_EQ(1u, mock_identity_accessor_.callbacks().size());
std::move(mock_identity_accessor_.callbacks().front())
.Run("auth token", clock_.Now() + base::TimeDelta::FromHours(1),
GoogleServiceAuthError(GoogleServiceAuthError::NONE));
mock_identity_accessor_.receivers_->FlushForTesting();
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"auth token", clock_.Now() + kTokenLifetime);
// Second mount will reuse previous token.
ASSERT_NO_FATAL_FAILURE(DoMount());
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
EXPECT_EQ("auth token", init_access_token_.value_or(""));
}
TEST_F(DriveFsHostTest, Remount_RequestInflightCompleteAfterMount) {
ASSERT_NO_FATAL_FAILURE(DoMount());
mock_identity_accessor_.set_pause_requests(true);
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
......@@ -777,20 +685,28 @@ TEST_F(DriveFsHostTest, Remount_RequestInflightCompleteAfterMount) {
SendOnUnmounted(delay);
base::RunLoop().RunUntilIdle();
ASSERT_NO_FATAL_FAILURE(DoUnmount());
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
// Second mount will reuse previous token.
ASSERT_NO_FATAL_FAILURE(DoMount());
EXPECT_FALSE(init_access_token_);
EXPECT_TRUE(identity_test_env_.IsAccessTokenRequestPending());
// Now the response is ready.
ASSERT_EQ(1u, mock_identity_accessor_.callbacks().size());
std::move(mock_identity_accessor_.callbacks().front())
.Run("auth token", clock_.Now() + base::TimeDelta::FromHours(1),
GoogleServiceAuthError(GoogleServiceAuthError::NONE));
mock_identity_accessor_.receivers_->FlushForTesting();
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
"auth token", clock_.Now() + kTokenLifetime);
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
// A new request will reuse the cached token.
ExpectAccessToken(mojom::AccessTokenStatus::kSuccess, "auth token");
delegate_->GetAccessToken(
"client ID", "app ID", {"scope1", "scope2"},
base::BindLambdaForTesting(
[&](mojom::AccessTokenStatus status, const std::string& token) {
EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
EXPECT_EQ("auth token", token);
}));
delegate_.FlushForTesting();
EXPECT_FALSE(identity_test_env_.IsAccessTokenRequestPending());
}
} // namespace
......
......@@ -27,6 +27,7 @@
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/mime_util.h"
#include "url/gurl.h"
namespace drivefs {
namespace {
......
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