Commit 924fafe6 authored by Kyle Horimoto's avatar Kyle Horimoto Committed by Commit Bot

[CrOS MultiDevice] Create multidevice::CryptAuthTokenFetcherImpl.

This class fetches access tokens for the current user using the
IdentityManager service. This will be used to fetch synced CryptAuth
data in the DeviceSync API.

Bug: 752273
Change-Id: I3978c7438a78f54800db3bb5b9f5e9221c9104d6
Reviewed-on: https://chromium-review.googlesource.com/906969Reviewed-by: default avatarRoger Tawa <rogerta@chromium.org>
Reviewed-by: default avatarTim Song <tengs@chromium.org>
Reviewed-by: default avatarColin Blundell <blundell@chromium.org>
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#536761}
parent 31fede9e
...@@ -7,6 +7,7 @@ if (!is_ios && !is_android) { ...@@ -7,6 +7,7 @@ if (!is_ios && !is_android) {
testonly = true testonly = true
deps = [ deps = [
"//components/multidevice/service:multidevice_service_unittest", "//components/multidevice/service:multidevice_service_unittest",
"//components/multidevice/service:unit_tests",
] ]
} }
} }
...@@ -10,6 +10,8 @@ if (!is_ios && !is_android) { ...@@ -10,6 +10,8 @@ if (!is_ios && !is_android) {
static_library("service") { static_library("service") {
sources = [ sources = [
"cryptauth_token_fetcher_impl.cc",
"cryptauth_token_fetcher_impl.h",
"device_sync_impl.cc", "device_sync_impl.cc",
"device_sync_impl.h", "device_sync_impl.h",
"fake_device_sync.cc", "fake_device_sync.cc",
...@@ -20,6 +22,7 @@ if (!is_ios && !is_android) { ...@@ -20,6 +22,7 @@ if (!is_ios && !is_android) {
deps = [ deps = [
"//base", "//base",
"//services/identity/public/cpp",
] ]
public_deps = [ public_deps = [
...@@ -35,6 +38,28 @@ if (!is_ios && !is_android) { ...@@ -35,6 +38,28 @@ if (!is_ios && !is_android) {
source = "multidevice_service_manifest.json" source = "multidevice_service_manifest.json"
} }
source_set("unit_tests") {
testonly = true
sources = [
"cryptauth_token_fetcher_impl_unittest.cc",
]
deps = [
":service",
"//base",
"//base/test:test_support",
"//components/cryptauth",
"//components/cryptauth:test_support",
"//components/multidevice/service/public/interfaces",
"//mojo/common",
"//services/identity/public/cpp:test_support",
"//services/service_manager/public/cpp:service_test_support",
"//testing/gmock",
"//testing/gtest",
]
}
service("multidevice") { service("multidevice") {
sources = [ sources = [
"test_main.cc", "test_main.cc",
......
include_rules = [ include_rules = [
"+components/cryptauth",
"+components/signin/core/browser",
"+google_apis/gaia",
"+mojo/public/cpp", "+mojo/public/cpp",
"+services/identity",
"+services/service_manager/public/cpp", "+services/service_manager/public/cpp",
"+components/cryptauth" "+components/cryptauth"
] ]
...@@ -8,4 +12,4 @@ specific_include_rules = { ...@@ -8,4 +12,4 @@ specific_include_rules = {
"test_main\.cc": [ "test_main\.cc": [
"+services/service_manager/public", "+services/service_manager/public",
] ]
} }
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/multidevice/service/cryptauth_token_fetcher_impl.h"
#include <set>
#include "services/identity/public/cpp/identity_manager.h"
namespace multidevice {
namespace {
const char kIdentityManagerConsumerName[] = "multi_device_service";
const char kCryptAuthApiScope[] = "https://www.googleapis.com/auth/cryptauth";
} // namespace
CryptAuthAccessTokenFetcherImpl::CryptAuthAccessTokenFetcherImpl(
identity::IdentityManager* identity_manager)
: identity_manager_(identity_manager), weak_ptr_factory_(this) {}
CryptAuthAccessTokenFetcherImpl::~CryptAuthAccessTokenFetcherImpl() {
// If any callbacks are still pending when the object is deleted, run them
// now, passing an empty string to indicate failure. This ensures that no
// callbacks are left hanging forever.
InvokeThenClearPendingCallbacks(std::string());
}
void CryptAuthAccessTokenFetcherImpl::FetchAccessToken(
const AccessTokenCallback& callback) {
pending_callbacks_.emplace_back(callback);
// If the token is already being fetched, continue waiting for it.
if (access_token_fetcher_)
return;
const OAuth2TokenService::ScopeSet kScopes{kCryptAuthApiScope};
access_token_fetcher_ =
identity_manager_->CreateAccessTokenFetcherForPrimaryAccount(
kIdentityManagerConsumerName, kScopes,
base::Bind(&CryptAuthAccessTokenFetcherImpl::OnAccessTokenFetched,
weak_ptr_factory_.GetWeakPtr()),
identity::PrimaryAccountAccessTokenFetcher::Mode::kImmediate);
}
void CryptAuthAccessTokenFetcherImpl::InvokeThenClearPendingCallbacks(
const std::string& access_token) {
for (auto& callback : pending_callbacks_)
callback.Run(access_token);
pending_callbacks_.clear();
}
void CryptAuthAccessTokenFetcherImpl::OnAccessTokenFetched(
const GoogleServiceAuthError& error,
const std::string& access_token) {
// Move |access_token_fetcher_| to a temporary std::unique_ptr so that it is
// deleted at the end of this function. This is done instead of deleting
// |access_token_fetcher_| at the end of this function to ensure that if any
// observers invoke FetchAccessToken(), a new object will be created.
std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher>
token_fetcher_deleter = std::move(access_token_fetcher_);
InvokeThenClearPendingCallbacks(
error == GoogleServiceAuthError::AuthErrorNone() ? access_token
: std::string());
}
} // namespace multidevice
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_MULTIDEVICE_CRYPTAUTH_ACCESS_TOKEN_FETCHER_IMPL
#define COMPONENTS_MULTIDEVICE_CRYPTAUTH_ACCESS_TOKEN_FETCHER_IMPL
#include <memory>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "components/cryptauth/cryptauth_access_token_fetcher.h"
#include "google_apis/gaia/google_service_auth_error.h"
namespace identity {
class IdentityManager;
class PrimaryAccountAccessTokenFetcher;
} // namespace identity
namespace multidevice {
// CryptAuthAccessTokenFetcher implementation which utilizes IdentityManager.
class CryptAuthAccessTokenFetcherImpl
: public cryptauth::CryptAuthAccessTokenFetcher {
public:
CryptAuthAccessTokenFetcherImpl(identity::IdentityManager* identity_manager);
~CryptAuthAccessTokenFetcherImpl() override;
// cryptauth::CryptAuthAccessTokenFetcher:
void FetchAccessToken(const AccessTokenCallback& callback) override;
private:
void InvokeThenClearPendingCallbacks(const std::string& access_token);
void OnAccessTokenFetched(const GoogleServiceAuthError& error,
const std::string& access_token);
identity::IdentityManager* identity_manager_;
std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher>
access_token_fetcher_;
std::vector<AccessTokenCallback> pending_callbacks_;
base::WeakPtrFactory<CryptAuthAccessTokenFetcherImpl> weak_ptr_factory_;
};
} // namespace multidevice
#endif // COMPONENTS_MULTIDEVICE_CRYPTAUTH_ACCESS_TOKEN_FETCHER_IMPL
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/multidevice/service/cryptauth_token_fetcher_impl.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace multidevice {
namespace {
const char kAccessToken[] = "access_token";
const char kTestEmail[] = "example@gmail.com";
} // namespace
class MultiDeviceCryptAuthAccessTokenFetcherImplTest : public testing::Test {
protected:
MultiDeviceCryptAuthAccessTokenFetcherImplTest() {}
void SetUp() override {
identity_test_environment_ =
std::make_unique<identity::IdentityTestEnvironment>();
identity_test_environment_->MakePrimaryAccountAvailable(kTestEmail);
token_fetcher_ = std::make_unique<CryptAuthAccessTokenFetcherImpl>(
identity_test_environment_->identity_manager());
}
void TearDown() override {
// Deleting the object should not result in any additional tokens provided.
token_fetcher_.reset();
EXPECT_EQ(nullptr, GetTokenAndReset());
EXPECT_TRUE(on_access_token_received_callback_.is_null());
}
void set_on_access_token_received_callback(
base::OnceClosure on_access_token_received_callback) {
EXPECT_TRUE(on_access_token_received_callback_.is_null());
on_access_token_received_callback_ =
std::move(on_access_token_received_callback);
}
void StartFetchingAccessToken() {
token_fetcher_->FetchAccessToken(base::Bind(
&MultiDeviceCryptAuthAccessTokenFetcherImplTest::OnAccessTokenFetched,
base::Unretained(this)));
}
void OnAccessTokenFetched(const std::string& access_token) {
last_access_token_ = std::make_unique<std::string>(access_token);
if (!on_access_token_received_callback_.is_null())
std::move(on_access_token_received_callback_).Run();
}
std::unique_ptr<std::string> GetTokenAndReset() {
return std::move(last_access_token_);
}
const base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<std::string> last_access_token_;
base::OnceClosure on_access_token_received_callback_;
std::unique_ptr<identity::IdentityTestEnvironment> identity_test_environment_;
std::unique_ptr<CryptAuthAccessTokenFetcherImpl> token_fetcher_;
private:
DISALLOW_COPY_AND_ASSIGN(MultiDeviceCryptAuthAccessTokenFetcherImplTest);
};
TEST_F(MultiDeviceCryptAuthAccessTokenFetcherImplTest, TestSuccess) {
base::RunLoop run_loop;
set_on_access_token_received_callback(run_loop.QuitClosure());
// Start fetching the token. Nothing should be returned yet since the message
// loop has not been run.
StartFetchingAccessToken();
EXPECT_EQ(nullptr, GetTokenAndReset());
identity_test_environment_->WaitForAccessTokenRequestAndRespondWithToken(
kAccessToken, base::Time::Max() /* expiration */);
// Run the request and confirm that the access token was returned.
run_loop.Run();
EXPECT_EQ(kAccessToken, *GetTokenAndReset());
}
TEST_F(MultiDeviceCryptAuthAccessTokenFetcherImplTest, TestFailure) {
base::RunLoop run_loop;
set_on_access_token_received_callback(run_loop.QuitClosure());
// Start fetching the token. Nothing should be returned yet since the message
// loop has not been run.
StartFetchingAccessToken();
EXPECT_EQ(nullptr, GetTokenAndReset());
identity_test_environment_->WaitForAccessTokenRequestAndRespondWithError(
GoogleServiceAuthError(
GoogleServiceAuthError::State::INVALID_GAIA_CREDENTIALS));
// Run the request and confirm that an empty string was returned, signifying a
// failure to fetch the token.
run_loop.Run();
EXPECT_EQ(std::string(), *GetTokenAndReset());
}
TEST_F(MultiDeviceCryptAuthAccessTokenFetcherImplTest,
TestDeletedBeforeOperationFinished) {
StartFetchingAccessToken();
EXPECT_EQ(nullptr, GetTokenAndReset());
// Deleting the object should result in the callback being invoked with an
// empty string (indicating failure).
token_fetcher_.reset();
EXPECT_EQ(std::string(), *GetTokenAndReset());
}
} // namespace multidevice
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