[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_;