[auth][e2e] Implement refresh token injection for testing with GoogleAuthProvider
This is a short term solution for providing auth tokens
enabling end to end testing.
Test: 1. Verify normal login process through UI succeeds.
2. Repave, open login, then verify successful authentication
using injection API through SL4F facade in related CL.
Change-Id: I542d004c7a5563f41bf70fca2549411efebe7bf1
diff --git a/auth_providers/google/BUILD.gn b/auth_providers/google/BUILD.gn
index d31263d..8bff9ed 100644
--- a/auth_providers/google/BUILD.gn
+++ b/auth_providers/google/BUILD.gn
@@ -52,6 +52,7 @@
"//garnet/public/lib/fxl",
"//garnet/public/lib/network_wrapper",
"//sdk/fidl/fuchsia.auth",
+ "//sdk/fidl/fuchsia.auth.testing",
"//sdk/fidl/fuchsia.ui.app",
"//sdk/lib/fidl/cpp",
"//topaz/runtime/chromium:chromium.web",
diff --git a/auth_providers/google/google_auth_provider_impl.cc b/auth_providers/google/google_auth_provider_impl.cc
index 5bed4ed..cf151fd 100644
--- a/auth_providers/google/google_auth_provider_impl.cc
+++ b/auth_providers/google/google_auth_provider_impl.cc
@@ -29,6 +29,8 @@
namespace {
+constexpr char kInjectionEntry[] = "LegacyAuthCredentialInjector";
+
std::string GetClientId(const std::string& app_client_id) {
// By default, use the client_id of the invoking application.
std::string client_id = app_client_id;
@@ -165,12 +167,14 @@
auth_ui_context_.set_error_handler([this](zx_status_t status) {
FX_LOG(INFO, NULL, "Overlay cancelled by the caller");
ReleaseResources();
- get_persistent_credential_callback_(AuthProviderStatus::UNKNOWN_ERROR,
- nullptr, nullptr);
+ RemoveCredentialInjectorInterface();
+ SafelyCallbackGetPersistentCredential(AuthProviderStatus::UNKNOWN_ERROR,
+ nullptr, nullptr);
return;
});
auth_ui_context_->StartOverlay2(std::move(view_holder_token));
+ ExposeCredentialInjectorInterface();
}
void GoogleAuthProviderImpl::GetAppAccessToken(
@@ -415,8 +419,6 @@
void GoogleAuthProviderImpl::OnNavigationStateChanged(
NavigationEvent change, OnNavigationStateChangedCallback callback) {
- FXL_CHECK(get_persistent_credential_callback_);
-
// Not all events change the URL, those that don't can be ignored.
if (change.url.is_null()) {
callback();
@@ -430,9 +432,12 @@
// we need to close the browser instance.
if (status != AuthProviderStatus::OK || !auth_code.empty()) {
ReleaseResources();
+ // InjectPersistentCredential will still be reachable even after removing
+ // the interface from output, but any requests to it will be discarded.
+ RemoveCredentialInjectorInterface();
if (status != AuthProviderStatus::OK) {
FX_LOGF(INFO, NULL, "Failed to capture auth code: Status %d", status);
- get_persistent_credential_callback_(status, nullptr, nullptr);
+ SafelyCallbackGetPersistentCredential(status, nullptr, nullptr);
} else if (!auth_code.empty()) {
FX_LOGF(INFO, NULL, "Captured auth code of length %d", auth_code.size());
ExchangeAuthCode(auth_code);
@@ -442,6 +447,18 @@
callback();
}
+void GoogleAuthProviderImpl::InjectPersistentCredential(
+ fuchsia::auth::UserProfileInfoPtr user_profile_info,
+ std::string credential) {
+ ReleaseResources();
+ RemoveCredentialInjectorInterface();
+ FX_LOGF(INFO, NULL, "Received injection request with credential of length %d",
+ credential.size());
+ SafelyCallbackGetPersistentCredential(AuthProviderStatus::OK,
+ std::move(credential),
+ std::move(user_profile_info));
+}
+
std::string GoogleAuthProviderImpl::GetAuthorizeUrl(
fidl::StringPtr user_profile) {
// TODO(ukode,jsankey): use app_scopes instead of |kScopes|.
@@ -479,8 +496,8 @@
auto oauth_response = ParseOAuthResponse(std::move(response));
if (oauth_response.status != AuthProviderStatus::OK) {
LogOauthResponse("ExchangeAuthCode", oauth_response);
- get_persistent_credential_callback_(oauth_response.status, nullptr,
- nullptr);
+ SafelyCallbackGetPersistentCredential(oauth_response.status, nullptr,
+ nullptr);
return;
}
@@ -498,8 +515,8 @@
})";
auto root_schema = rapidjson_utils::InitSchema(kRootSchema);
if (!root_schema) {
- get_persistent_credential_callback_(AuthProviderStatus::INTERNAL_ERROR,
- nullptr, nullptr);
+ SafelyCallbackGetPersistentCredential(AuthProviderStatus::INTERNAL_ERROR,
+ nullptr, nullptr);
return;
}
if (!rapidjson_utils::ValidateSchema(oauth_response.json_response,
@@ -507,7 +524,7 @@
FX_LOGF(WARNING, NULL,
"Got response without refresh and access tokens: %s",
JsonValueToPrettyString(oauth_response.json_response).c_str());
- get_persistent_credential_callback_(
+ SafelyCallbackGetPersistentCredential(
AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr, nullptr);
return;
}
@@ -543,8 +560,8 @@
auto oauth_response = ParseOAuthResponse(std::move(response));
if (oauth_response.status != AuthProviderStatus::OK) {
LogOauthResponse("UserInfo", oauth_response);
- get_persistent_credential_callback_(oauth_response.status, credential,
- std::move(user_profile_info));
+ SafelyCallbackGetPersistentCredential(oauth_response.status, credential,
+ std::move(user_profile_info));
return;
}
@@ -554,7 +571,7 @@
} else {
LogOauthResponse("UserInfo", oauth_response);
FX_LOG(INFO, NULL, "Missing unique identifier in UserInfo response");
- get_persistent_credential_callback_(
+ SafelyCallbackGetPersistentCredential(
AuthProviderStatus::OAUTH_SERVER_ERROR, nullptr,
std::move(user_profile_info));
return;
@@ -579,8 +596,8 @@
}
FX_LOG(INFO, NULL, "Received valid UserInfo response");
- get_persistent_credential_callback_(oauth_response.status, credential,
- std::move(user_profile_info));
+ SafelyCallbackGetPersistentCredential(oauth_response.status, credential,
+ std::move(user_profile_info));
});
}
@@ -621,6 +638,20 @@
return view_holder_token;
}
+void GoogleAuthProviderImpl::SafelyCallbackGetPersistentCredential(
+ AuthProviderStatus auth_provider_status, fidl::StringPtr credential,
+ fuchsia::auth::UserProfileInfoPtr user_profile_info) {
+ if (get_persistent_credential_callback_) {
+ get_persistent_credential_callback_(auth_provider_status,
+ std::move(credential),
+ std::move(user_profile_info));
+ get_persistent_credential_callback_ = nullptr;
+ } else {
+ FX_LOG(WARNING, NULL,
+ "Attempted to call GetPersistentCredential callback twice.");
+ }
+}
+
void GoogleAuthProviderImpl::ReleaseResources() {
// Close any open view
if (auth_ui_context_) {
@@ -634,6 +665,27 @@
chromium_context_ = nullptr;
}
+void GoogleAuthProviderImpl::ExposeCredentialInjectorInterface() {
+ context_->outgoing().debug_dir()->AddEntry(
+ kInjectionEntry,
+ fbl::AdoptRef(new fs::Service([this](zx::channel channel) {
+ fidl::InterfaceRequest<
+ fuchsia::auth::testing::LegacyAuthCredentialInjector>
+ request(std::move(channel));
+ injector_bindings_.AddBinding(this, std::move(request));
+ return ZX_OK;
+ })));
+}
+
+void GoogleAuthProviderImpl::RemoveCredentialInjectorInterface() {
+ if (context_->outgoing().debug_dir()->RemoveEntry(kInjectionEntry) ==
+ ZX_ERR_NOT_FOUND) {
+ FX_LOGF(WARNING, NULL,
+ "Attempted to remove nonexistent '%s' from debug directory",
+ kInjectionEntry);
+ }
+}
+
void GoogleAuthProviderImpl::Request(
fit::function<http::URLRequest()> request_factory,
fit::function<void(http::URLResponse response)> callback) {
diff --git a/auth_providers/google/google_auth_provider_impl.h b/auth_providers/google/google_auth_provider_impl.h
index ea31ee3..c9233b3 100644
--- a/auth_providers/google/google_auth_provider_impl.h
+++ b/auth_providers/google/google_auth_provider_impl.h
@@ -9,6 +9,7 @@
#include <chromium/web/cpp/fidl.h>
#include <fuchsia/auth/cpp/fidl.h>
+#include <fuchsia/auth/testing/cpp/fidl.h>
#include <lib/fit/function.h>
#include <lib/zx/eventpair.h>
@@ -27,8 +28,10 @@
using fuchsia::auth::AttestationSigner;
using fuchsia::auth::AuthenticationUIContext;
-class GoogleAuthProviderImpl : chromium::web::NavigationEventObserver,
- fuchsia::auth::AuthProvider {
+class GoogleAuthProviderImpl
+ : chromium::web::NavigationEventObserver,
+ fuchsia::auth::AuthProvider,
+ fuchsia::auth::testing::LegacyAuthCredentialInjector {
public:
GoogleAuthProviderImpl(
async_dispatcher_t* main_dispatcher, component::StartupContext* context,
@@ -85,6 +88,13 @@
NavigationEvent change,
OnNavigationStateChangedCallback callback) override;
+ // |fuchsia::auth::testing::LegacyAuthCredentialInjector|
+ // This is a short-term solution to enable end-to-end testing. It should not
+ // be carried over during any refactoring efforts.
+ void InjectPersistentCredential(
+ fuchsia::auth::UserProfileInfoPtr user_profile_info,
+ std::string credential) override;
+
// Returns the URL to be used for the authentication call, respecting any
// settings that influence the URL.
std::string GetAuthorizeUrl(fidl::StringPtr user_profile_id);
@@ -104,6 +114,23 @@
// a |zx::eventpair| token for the view's ViewHolder.
zx::eventpair SetupChromium();
+ // Calls the GetPersistentCredential callback if one is available, or logs
+ // and returns immediately otherwise. This enables interactive signin or
+ // InjectPersistentCredential to terminate gracefully even after the other
+ // has sent a response to the pending callback.
+ void SafelyCallbackGetPersistentCredential(
+ fuchsia::auth::AuthProviderStatus auth_provider_status,
+ fidl::StringPtr credential,
+ fuchsia::auth::UserProfileInfoPtr user_profile_info);
+
+ // Exposes a |fuchsia::auth::testing::LegacyAuthCredentialInjector| handle on
+ // the output debug directory.
+ void ExposeCredentialInjectorInterface();
+
+ // Removes the |fuchsia::auth::testing::LegacyAuthCredentialInjector| handle
+ // on the output debug directory.
+ void RemoveCredentialInjectorInterface();
+
// Safely releases any resources associated with an open Webkit or Chromium
// instance, including the associated view.
void ReleaseResources();
@@ -125,6 +152,8 @@
fidl::BindingSet<chromium::web::NavigationEventObserver>
navigation_event_observer_bindings_;
+ fidl::BindingSet<fuchsia::auth::testing::LegacyAuthCredentialInjector>
+ injector_bindings_;
fidl::Binding<fuchsia::auth::AuthProvider> binding_;
callback::CancellableContainer requests_;