blob: c393f358fc4858d781643fc2b5abf8704d973263 [file] [log] [blame]
// Copyright 2018 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 fidl::encoding::OutOfLine;
use fidl::endpoints::ServerEnd;
use fidl::Error;
use fidl_fuchsia_auth::{AuthProviderGetAppAccessTokenFromAssertionJwtResponder,
AuthProviderGetAppAccessTokenResponder,
AuthProviderGetAppFirebaseTokenResponder,
AuthProviderGetAppIdTokenResponder,
AuthProviderGetPersistentCredentialFromAttestationJwtResponder,
AuthProviderGetPersistentCredentialResponder, AuthProviderMarker,
AuthProviderRequest, AuthProviderRevokeAppOrPersistentCredentialResponder,
AuthProviderStatus, UserProfileInfo};
use fuchsia_async as fasync;
use futures::future;
use futures::prelude::*;
use log::warn;
use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;
use std::time::Duration;
const TOKEN_LIFETIME: Duration = Duration::from_secs(3600); // one hour lifetime
const CHALLENGE_LIFETIME: Duration = Duration::from_secs(24 * 3600); // 24 hour lifetime
const USER_PROFILE_INFO_ID_DOMAIN: &str = "@example.com";
const USER_PROFILE_INFO_DISPLAY_NAME: &str = "test_user_display_name";
const USER_PROFILE_INFO_URL: &str = "http://test_user/profile/url";
const USER_PROFILE_INFO_IMAGE_URL: &str = "http://test_user/profile/image/url";
const RANDOM_STRING_LENGTH: usize = 10;
/// Generate random alphanumeric string of fixed length RANDOM_STRING_LENGTH
/// for creating unique tokens or id.
fn generate_random_string() -> String {
thread_rng()
.sample_iter(&Alphanumeric)
.take(RANDOM_STRING_LENGTH)
.collect()
}
/// The AuthProvider struct is holding implementation of the `AuthProvider` fidl
/// interface. This implementation is serving as a testing endpoint for token
/// manager.
pub struct AuthProvider;
impl AuthProvider {
/// Spawn a new task of handling request from the
/// `AuthProviderRequestStream` by calling the `handle_request` method.
/// Create a warning on error.
pub fn spawn(server_end: ServerEnd<AuthProviderMarker>) {
match server_end.into_stream() {
Err(err) => {
warn!("Error creating AuthProvider request stream {:?}", err);
}
Ok(request_stream) => fasync::spawn(
request_stream
.try_for_each(|r| future::ready(Self::handle_request(r)))
.unwrap_or_else(|e| warn!("Error running AuthProvider{:?}", e)),
),
};
}
/// Handle single `AuthProviderRequest` by calling the corresponding method
/// according to the actual variant of the `AuthProviderRequest` enum.
fn handle_request(req: AuthProviderRequest) -> Result<(), Error> {
match req {
AuthProviderRequest::GetPersistentCredential { responder, .. } => {
Self::get_persistent_credential(responder)
}
AuthProviderRequest::GetAppAccessToken {
credential,
client_id,
responder,
..
} => Self::get_app_access_token(credential, client_id, responder),
AuthProviderRequest::GetAppIdToken {
credential,
responder,
..
} => Self::get_app_id_token(credential, responder),
AuthProviderRequest::GetAppFirebaseToken {
firebase_api_key,
responder,
..
} => Self::get_app_firebase_token(firebase_api_key, responder),
AuthProviderRequest::RevokeAppOrPersistentCredential { responder, .. } => {
Self::revoke_app_or_persistent_credential(responder)
}
AuthProviderRequest::GetPersistentCredentialFromAttestationJwt {
user_profile_id,
responder,
..
} => Self::get_persistent_credential_from_attestation_jwt(user_profile_id, responder),
AuthProviderRequest::GetAppAccessTokenFromAssertionJwt {
credential,
responder,
..
} => Self::get_app_access_token_from_assertion_jwt(credential, responder),
}
}
/// Implementation of the `GetPersistenCredential` method for the `AuthProvider` fidl
/// interface. The field auth_ui_context is removed here as we will never use it in the dev
/// auth provider.
fn get_persistent_credential(
responder: AuthProviderGetPersistentCredentialResponder,
) -> Result<(), Error> {
responder.send(AuthProviderStatus::BadRequest, None, None)
}
/// Implementation of the `GetAppAccessToken` method for the `AuthProvider` fidl interface.
fn get_app_access_token(
_: String, _: Option<String>, responder: AuthProviderGetAppAccessTokenResponder,
) -> Result<(), Error> {
responder.send(AuthProviderStatus::BadRequest, None)
}
/// Implementation of the `GetAppIdToken` method for the `AuthProvider` fidl interface.
fn get_app_id_token(
_: String, responder: AuthProviderGetAppIdTokenResponder,
) -> Result<(), Error> {
responder.send(AuthProviderStatus::BadRequest, None)
}
/// Implementation of the `GetAppFirebaseToken` method for the `AuthProvider` fidl interface.
fn get_app_firebase_token(
_: String, responder: AuthProviderGetAppFirebaseTokenResponder,
) -> Result<(), Error> {
responder.send(AuthProviderStatus::BadRequest, None)
}
/// Implementation of the `RevokeAppOrPersistentCredential` method for the `AuthProvider` fidl
/// interface.
fn revoke_app_or_persistent_credential(
responder: AuthProviderRevokeAppOrPersistentCredentialResponder,
) -> Result<(), Error> {
responder.send(AuthProviderStatus::Ok)
}
/// Implementation of the `GetPersistentCredentialFromAttestationJwt` method for the
/// `AuthProvider` fidl interface.
fn get_persistent_credential_from_attestation_jwt(
user_profile_id: Option<String>,
responder: AuthProviderGetPersistentCredentialFromAttestationJwtResponder,
) -> Result<(), Error> {
let credential = Some("rt_".to_string() + &generate_random_string());
let mut auth_token = Some(fidl_fuchsia_auth::AuthToken {
token_type: fidl_fuchsia_auth::TokenType::AccessToken,
token: "at_".to_string() + &generate_random_string(),
expires_in: TOKEN_LIFETIME.as_secs(),
});
let mut auth_challenge = Some(fidl_fuchsia_auth::AuthChallenge {
challenge: "ch_".to_string() + &generate_random_string(),
expires_in: CHALLENGE_LIFETIME.as_secs(),
});
let mut user_id = user_profile_id.unwrap_or("".to_string());
if user_id.is_empty() {
user_id = generate_random_string() + USER_PROFILE_INFO_ID_DOMAIN;
}
let mut user_profile_info = Some(UserProfileInfo {
id: user_id,
display_name: Some(USER_PROFILE_INFO_DISPLAY_NAME.to_string()),
url: Some(USER_PROFILE_INFO_URL.to_string()),
image_url: Some(USER_PROFILE_INFO_IMAGE_URL.to_string()),
});
responder.send(
AuthProviderStatus::Ok,
credential.as_ref().map(|s| &**s),
auth_token.as_mut().map(OutOfLine),
auth_challenge.as_mut().map(OutOfLine),
user_profile_info.as_mut().map(OutOfLine),
)
}
/// Implementation of the `GetAppAccessTokenFromAssertionJwt` method for the
/// `AuthProvider` fidl interface.
fn get_app_access_token_from_assertion_jwt(
credential: String, responder: AuthProviderGetAppAccessTokenFromAssertionJwtResponder,
) -> Result<(), Error> {
let mut auth_token = Some(fidl_fuchsia_auth::AuthToken {
token_type: fidl_fuchsia_auth::TokenType::AccessToken,
token: credential.clone() + ":at_" + &generate_random_string(),
expires_in: TOKEN_LIFETIME.as_secs(),
});
let mut auth_challenge = Some(fidl_fuchsia_auth::AuthChallenge {
challenge: credential.clone() + ":ch_" + &generate_random_string(),
expires_in: CHALLENGE_LIFETIME.as_secs(),
});
let updated_credential = Some("up_cr_".to_string() + &generate_random_string());
responder.send(
AuthProviderStatus::Ok,
updated_credential.as_ref().map(|s| &**s),
auth_token.as_mut().map(OutOfLine),
auth_challenge.as_mut().map(OutOfLine),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl::endpoints::ClientEnd;
use fidl_fuchsia_auth::AssertionJwtParams;
use fidl_fuchsia_auth::AttestationJwtParams;
use fidl_fuchsia_auth::AttestationSignerMarker;
use fidl_fuchsia_auth::AuthChallenge;
use fidl_fuchsia_auth::AuthProviderProxy;
use fidl_fuchsia_auth::AuthToken;
use fidl_fuchsia_auth::CredentialEcKey;
use fuchsia_zircon as zx;
fn set_up() -> Result<(fasync::Executor, AuthProviderProxy), failure::Error> {
let exec = fasync::Executor::new()?;
let (server_chan, client_chan) = zx::Channel::create()?;
let client_chan = fasync::Channel::from_channel(client_chan)?;
let server_end = ServerEnd::<AuthProviderMarker>::new(server_chan);
AuthProvider::spawn(server_end);
let proxy = AuthProviderProxy::new(client_chan);
Ok((exec, proxy))
}
// Returns the client_end of the 'AttestationSigner' component.
fn get_attestation_signer() -> ClientEnd<AttestationSignerMarker> {
// TODO(ukode): Add a functional attestation signer component here.
let (_server_chan, client_chan) =
zx::Channel::create().expect("Failed to create zx channels");
return ClientEnd::<AttestationSignerMarker>::new(client_chan);
}
// Generates and returns an elliptic curve credential key.
fn get_credential_key() -> CredentialEcKey {
return CredentialEcKey {
curve: String::from("P-256"),
key_x_val: String::from("x-coordinates"),
key_y_val: String::from("y-coordinates"),
fingerprint_sha_256: String::from("sha_256_hash_of_public_key"),
};
}
fn async_test<F, Fut>(f: F)
where
F: FnOnce(AuthProviderProxy) -> Fut,
Fut: Future<Output = Result<(), Error>>,
{
let (mut exec, dev_auth_provider_iotid) =
set_up().expect("Test set up should not have failed.");
let test_fut = f(dev_auth_provider_iotid);
exec.run_singlethreaded(test_fut)
.expect("executor run failed.")
}
#[test]
fn test_get_persistent_credential() {
async_test(|dev_auth_provider_iotid| {
dev_auth_provider_iotid
.get_persistent_credential(None, None)
.map_ok(move |response| {
let (status, _, _) = response;
assert_eq!(status, AuthProviderStatus::BadRequest);
})
});
}
#[test]
fn test_get_app_access_token() {
async_test(|dev_auth_provider_iotid| {
let credential = "rt_".to_string() + &generate_random_string();
let client_id = generate_random_string();
let mut scopes = vec![].into_iter();
dev_auth_provider_iotid
.get_app_access_token(&credential, Some(&client_id), &mut scopes)
.map_ok(move |response| {
let (status, _) = response;
assert_eq!(status, AuthProviderStatus::BadRequest);
})
});
}
#[test]
fn test_get_app_id_token() {
async_test(|dev_auth_provider_iotid| {
let credential = "rt_".to_string() + &generate_random_string();
dev_auth_provider_iotid
.get_app_id_token(&credential, None)
.map_ok(move |response| {
let (status, _) = response;
assert_eq!(status, AuthProviderStatus::BadRequest);
})
});
}
#[test]
fn test_get_app_firebase_token() {
async_test(|dev_auth_provider_iotid| {
dev_auth_provider_iotid
.get_app_firebase_token("test_id_token", "test_firebase_api_key")
.map_ok(move |response| {
let (status, _) = response;
assert_eq!(status, AuthProviderStatus::BadRequest);
})
});
}
#[test]
fn test_revoke_app_or_persistent_credential() {
async_test(|dev_auth_provider_iotid| {
let credential = "testing_credential";
dev_auth_provider_iotid
.revoke_app_or_persistent_credential(credential)
.map_ok(move |response| {
assert_eq!(response, AuthProviderStatus::Ok);
})
});
}
#[test]
fn test_get_persistent_credential_from_attestation_jwt() {
async_test(|dev_auth_provider_iotid| {
let attestation_signer = get_attestation_signer();
let mut jwt_params = AttestationJwtParams {
credential_eckey: get_credential_key(),
certificate_chain: vec![],
auth_code: "test_auth_code".to_string(),
};
dev_auth_provider_iotid
.get_persistent_credential_from_attestation_jwt(
attestation_signer,
&mut jwt_params,
None,
None,
)
.map_ok(move |response| {
let (status, credential, access_token, auth_challenge, user_profile_info) =
response;
assert_eq!(status, AuthProviderStatus::Ok);
assert!(credential.unwrap().contains("rt_"));
let AuthToken {
token_type,
token,
expires_in,
} = *(access_token.unwrap());
assert_eq!(token_type, fidl_fuchsia_auth::TokenType::AccessToken);
assert_eq!(expires_in, TOKEN_LIFETIME.as_secs());
assert!(token.contains("at_"));
let AuthChallenge {
challenge,
expires_in,
} = *(auth_challenge.unwrap());
assert_eq!(expires_in, CHALLENGE_LIFETIME.as_secs());
assert!(challenge.contains("ch_"));
let UserProfileInfo {
id, display_name, ..
} = *(user_profile_info.unwrap());
assert!(
display_name
.unwrap()
.contains(USER_PROFILE_INFO_DISPLAY_NAME)
);
assert!(id.contains(USER_PROFILE_INFO_ID_DOMAIN));
})
});
}
#[test]
fn test_get_app_access_token_from_assertion_jwt() {
async_test(|dev_auth_provider_iotid| {
let attestation_signer = get_attestation_signer();
let mut jwt_params = AssertionJwtParams {
credential_eckey: get_credential_key(),
challenge: Some("test_challenge".to_string()),
};
let credential = "rt_".to_string() + &generate_random_string();
let mut scopes = vec![].into_iter();
dev_auth_provider_iotid
.get_app_access_token_from_assertion_jwt(
attestation_signer,
&mut jwt_params,
&credential,
&mut scopes,
)
.map_ok(move |response| {
let (status, updated_credential, access_token, auth_challenge) = response;
assert_eq!(status, AuthProviderStatus::Ok);
assert!(updated_credential.unwrap().contains("up_cr_"));
let AuthToken {
token_type,
token,
expires_in,
} = *(access_token.unwrap());
assert_eq!(token_type, fidl_fuchsia_auth::TokenType::AccessToken);
assert_eq!(expires_in, TOKEN_LIFETIME.as_secs());
assert!(token.contains(&credential));
assert!(token.contains("at_"));
let AuthChallenge {
challenge,
expires_in,
} = *(auth_challenge.unwrap());
assert_eq!(expires_in, CHALLENGE_LIFETIME.as_secs());
assert!(challenge.contains("ch_"));
})
});
}
}