[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,