[auth][e2e] SL4F facade to inject test credentials into GoogleAuthProvider

This also adds feature=shell in order to access the Hub.

Test: Pave device and walk through setup until login reached.
Run SL4F on the device. From host,
curl <device> -X GET -d '{"id": "", "method": "auth_facade.InjectAuthToken", "params": {"credential": "<token>", "user_profile_info": {"id": "<id>", "display_name": null, "url": null, "image_url": null}}}'

Change-Id: Ie9449aaade7f9d7ef20829a094a1091c452e8fc4
diff --git a/garnet/bin/sl4f/BUILD.gn b/garnet/bin/sl4f/BUILD.gn
index 60c7410..91babcb 100644
--- a/garnet/bin/sl4f/BUILD.gn
+++ b/garnet/bin/sl4f/BUILD.gn
@@ -26,6 +26,8 @@
     "//garnet/public/rust/fuchsia-vfs/fuchsia-vfs-watcher",
     "//garnet/public/rust/fuchsia-zircon",
     "//garnet/public/rust/mapped-vmo",
+    "//sdk/fidl/fuchsia.auth:fuchsia.auth-rustc",
+    "//sdk/fidl/fuchsia.auth.testing:fuchsia.auth.testing-rustc",
     "//sdk/fidl/fuchsia.bluetooth:fuchsia.bluetooth-rustc",
     "//sdk/fidl/fuchsia.bluetooth.gatt:fuchsia.bluetooth.gatt-rustc",
     "//sdk/fidl/fuchsia.bluetooth.le:fuchsia.bluetooth.le-rustc",
@@ -40,6 +42,7 @@
     "//third_party/rust_crates:byteorder",
     "//third_party/rust_crates:failure",
     "//third_party/rust_crates:futures-preview",
+    "//third_party/rust_crates:glob",
     "//third_party/rust_crates:parking_lot",
     "//third_party/rust_crates:rand",
     "//third_party/rust_crates:regex",
diff --git a/garnet/bin/sl4f/meta/sl4f.cmx b/garnet/bin/sl4f/meta/sl4f.cmx
index e170fe2..ce0f0a99 100644
--- a/garnet/bin/sl4f/meta/sl4f.cmx
+++ b/garnet/bin/sl4f/meta/sl4f.cmx
@@ -19,6 +19,7 @@
             "fuchsia.ui.policy.Presenter",
             "fuchsia.ui.scenic.Scenic",
             "fuchsia.wlan.device.service.DeviceService"
-        ]
+        ],
+        "features": [ "shell" ]
     }
 }
diff --git a/garnet/bin/sl4f/src/auth/commands.rs b/garnet/bin/sl4f/src/auth/commands.rs
new file mode 100644
index 0000000..7ef366b
--- /dev/null
+++ b/garnet/bin/sl4f/src/auth/commands.rs
@@ -0,0 +1,32 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use failure::{bail, Error};
+
+use serde_json::{from_value, to_value, Value};
+use std::sync::Arc;
+
+use crate::auth::facade::AuthFacade;
+use crate::auth::types::InjectAuthTokenRequest;
+
+/// Takes ACTS method command and forwards to corresponding auth debug FIDL
+/// method.
+///
+/// The InjectAuthToken expects |InjectAuthTokenRequest| serialized in args
+/// and returns |InjectAuthTokenResult| enum.
+pub async fn auth_method_to_fidl(
+    method_name: String,
+    args: Value,
+    facade: Arc<AuthFacade>,
+) -> Result<Value, Error> {
+    match method_name.as_ref() {
+        "InjectAuthToken" => {
+            let request: InjectAuthTokenRequest = from_value(args)?;
+            let result =
+                await!(facade.inject_auth_token(request.user_profile_info, request.credential,))?;
+            Ok(to_value(result)?)
+        }
+        _ => bail!("Invalid Auth Facade method: {:?}", method_name),
+    }
+}
diff --git a/garnet/bin/sl4f/src/auth/facade.rs b/garnet/bin/sl4f/src/auth/facade.rs
new file mode 100644
index 0000000..c910ad0
--- /dev/null
+++ b/garnet/bin/sl4f/src/auth/facade.rs
@@ -0,0 +1,64 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use failure::Error;
+use fidl::encoding::OutOfLine;
+use fuchsia_zircon as zx;
+use glob::glob;
+
+use crate::auth::types::InjectAuthTokenResult;
+use fidl_fuchsia_auth::UserProfileInfo;
+use fidl_fuchsia_auth_testing::LegacyAuthCredentialInjectorSynchronousProxy;
+
+/// Facade providing access to authentication testing interfaces.
+#[derive(Debug)]
+pub struct AuthFacade {}
+
+impl AuthFacade {
+    pub fn new() -> AuthFacade {
+        AuthFacade {}
+    }
+
+    /// Discovers a |LegacyAuthCredentialInjector| service published by
+    /// GoogleAuthProvider and uses it to inject the provided user profile
+    /// info and credential.
+    /// |credential| should be a persistent credential provided by Google
+    /// identity provider.
+    /// If |user_profile_info| is provided it should contain an obfuscated
+    /// GAIA id.
+    ///
+    /// This is a short term solution for enabling end to
+    /// end testing.  It should be replaced by automated authentication through
+    /// Chrome driver and a long-term injection design - AUTH-161 and AUTH-185.
+    pub async fn inject_auth_token(
+        &self,
+        mut user_profile_info: Option<UserProfileInfo>,
+        credential: String,
+    ) -> Result<InjectAuthTokenResult, Error> {
+        let mut injection_proxy = match self.discover_injection_service()? {
+            Some(proxy) => proxy,
+            None => return Ok(InjectAuthTokenResult::NotReady),
+        };
+        injection_proxy.inject_persistent_credential(
+            user_profile_info.as_mut().map(|v| OutOfLine(v)),
+            &credential,
+        )?;
+        Ok(InjectAuthTokenResult::Success)
+    }
+
+    fn discover_injection_service(
+        &self,
+    ) -> Result<Option<LegacyAuthCredentialInjectorSynchronousProxy>, Error> {
+        let glob_path = "/hub/c/google_auth_provider.cmx/*/out/debug/LegacyAuthCredentialInjector";
+        let found_path = glob(glob_path)?.filter_map(|entry| entry.ok()).next();
+        match found_path {
+            Some(path) => {
+                let (client, server) = zx::Channel::create()?;
+                fdio::service_connect(path.to_string_lossy().as_ref(), server)?;
+                Ok(Some(LegacyAuthCredentialInjectorSynchronousProxy::new(client)))
+            }
+            None => Ok(None),
+        }
+    }
+}
diff --git a/garnet/bin/sl4f/src/auth/mod.rs b/garnet/bin/sl4f/src/auth/mod.rs
new file mode 100644
index 0000000..28121b1
--- /dev/null
+++ b/garnet/bin/sl4f/src/auth/mod.rs
@@ -0,0 +1,7 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod commands;
+pub mod facade;
+pub mod types;
diff --git a/garnet/bin/sl4f/src/auth/types.rs b/garnet/bin/sl4f/src/auth/types.rs
new file mode 100644
index 0000000..cda6d39
--- /dev/null
+++ b/garnet/bin/sl4f/src/auth/types.rs
@@ -0,0 +1,57 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use serde::de::Error;
+use serde::{Deserialize, Deserializer};
+use serde_derive::{Deserialize, Serialize};
+
+use fidl_fuchsia_auth::UserProfileInfo;
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum InjectAuthTokenResult {
+    NotReady,
+    Success,
+}
+
+#[derive(Deserialize)]
+#[serde(remote = "UserProfileInfo")]
+pub struct UserProfileInfoDef {
+    #[serde(deserialize_with = "string_ensure_nonempty")]
+    pub id: String,
+    pub display_name: Option<String>,
+    pub url: Option<String>,
+    pub image_url: Option<String>,
+}
+
+#[derive(Deserialize)]
+pub struct InjectAuthTokenRequest {
+    #[serde(default, deserialize_with = "option_user_profile_info")]
+    pub user_profile_info: Option<UserProfileInfo>,
+    #[serde(deserialize_with = "string_ensure_nonempty")]
+    pub credential: String,
+}
+
+// https://github.com/serde-rs/serde/issues/723
+fn option_user_profile_info<'de, D>(deserializer: D) -> Result<Option<UserProfileInfo>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    struct Wrapper(#[serde(with = "UserProfileInfoDef")] UserProfileInfo);
+
+    let v = Option::deserialize(deserializer)?;
+    Ok(v.map(|Wrapper(a)| a))
+}
+
+fn string_ensure_nonempty<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let string = String::deserialize(deserializer)?;
+    if string.is_empty() {
+        Err(Error::custom("Empty string not allowed in InjectAuthTokenRequest"))
+    } else {
+        Ok(string)
+    }
+}
diff --git a/garnet/bin/sl4f/src/main.rs b/garnet/bin/sl4f/src/main.rs
index 718a3d2..4cd58b0 100644
--- a/garnet/bin/sl4f/src/main.rs
+++ b/garnet/bin/sl4f/src/main.rs
@@ -16,6 +16,7 @@
 use std::thread;
 
 mod audio;
+mod auth;
 mod bluetooth;
 mod netstack;
 mod scenic;
diff --git a/garnet/bin/sl4f/src/server/sl4f.rs b/garnet/bin/sl4f/src/server/sl4f.rs
index 60b346f..5970210 100644
--- a/garnet/bin/sl4f/src/server/sl4f.rs
+++ b/garnet/bin/sl4f/src/server/sl4f.rs
@@ -25,6 +25,9 @@
 // Audio related includes
 use crate::audio::facade::AudioFacade;
 
+// Auth related includes
+use crate::auth::facade::AuthFacade;
+
 // Bluetooth related includes
 use crate::bluetooth::ble_advertise_facade::BleAdvertiseFacade;
 use crate::bluetooth::facade::BluetoothFacade;
@@ -49,6 +52,9 @@
     // audio_facade: Thread safe object for state for Audio tests
     audio_facade: Arc<AudioFacade>,
 
+    // auth_facade: Thread safe object for injecting credentials for tests.
+    auth_facade: Arc<AuthFacade>,
+
     // bt_facade: Thread safe object for state for ble functions.
     ble_advertise_facade: Arc<BleAdvertiseFacade>,
 
@@ -79,6 +85,7 @@
 impl Sl4f {
     pub fn new() -> Result<Arc<RwLock<Sl4f>>, Error> {
         let audio_facade = Arc::new(AudioFacade::new()?);
+        let auth_facade = Arc::new(AuthFacade::new());
         let ble_advertise_facade = Arc::new(BleAdvertiseFacade::new());
         let gatt_client_facade = Arc::new(GattClientFacade::new());
         let gatt_server_facade = Arc::new(GattServerFacade::new());
@@ -87,6 +94,7 @@
         let wlan_facade = Arc::new(WlanFacade::new()?);
         Ok(Arc::new(RwLock::new(Sl4f {
             audio_facade,
+            auth_facade,
             ble_advertise_facade,
             bt_facade: BluetoothFacade::new(),
             gatt_client_facade,
@@ -106,6 +114,10 @@
         self.audio_facade.clone()
     }
 
+    pub fn get_auth_facade(&self) -> Arc<AuthFacade> {
+        self.auth_facade.clone()
+    }
+
     pub fn get_ble_advertise_facade(&self) -> Arc<BleAdvertiseFacade> {
         self.ble_advertise_facade.clone()
     }
diff --git a/garnet/bin/sl4f/src/server/sl4f_executor.rs b/garnet/bin/sl4f/src/server/sl4f_executor.rs
index be548cd..f90b59c 100644
--- a/garnet/bin/sl4f/src/server/sl4f_executor.rs
+++ b/garnet/bin/sl4f/src/server/sl4f_executor.rs
@@ -17,6 +17,7 @@
 
 // Translation layers go here (i.e netstack_method_to_fidl)
 use crate::audio::commands::audio_method_to_fidl;
+use crate::auth::commands::auth_method_to_fidl;
 use crate::bluetooth::commands::ble_advertise_method_to_fidl;
 use crate::bluetooth::commands::ble_method_to_fidl;
 use crate::bluetooth::commands::gatt_client_method_to_fidl;
@@ -83,6 +84,9 @@
         FacadeType::AudioFacade => {
             await!(audio_method_to_fidl(method_name, args, sl4f_session.read().get_audio_facade(),))
         }
+        FacadeType::AuthFacade => {
+            await!(auth_method_to_fidl(method_name, args, sl4f_session.read().get_auth_facade(),))
+        }
         FacadeType::BleAdvertiseFacade => await!(ble_advertise_method_to_fidl(
             method_name,
             args,
diff --git a/garnet/bin/sl4f/src/server/sl4f_types.rs b/garnet/bin/sl4f/src/server/sl4f_types.rs
index 6890f52..2078f8d 100644
--- a/garnet/bin/sl4f/src/server/sl4f_types.rs
+++ b/garnet/bin/sl4f/src/server/sl4f_types.rs
@@ -108,6 +108,7 @@
 /// Make sure to update sl4f.rs:method_to_fidl() match statement
 pub enum FacadeType {
     AudioFacade,
+    AuthFacade,
     BleAdvertiseFacade,
     Bluetooth,
     GattClientFacade,
@@ -122,6 +123,7 @@
     pub fn from_str(facade: &String) -> FacadeType {
         match facade.as_ref() {
             "audio_facade" => FacadeType::AudioFacade,
+            "auth_facade" => FacadeType::AuthFacade,
             "ble_advertise_facade" => FacadeType::BleAdvertiseFacade,
             "bluetooth" => FacadeType::Bluetooth,
             "gatt_client_facade" => FacadeType::GattClientFacade,