blob: 283da34ac9cff1fce797add622e8c8a5d5471bcf [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 crate::common::{
generate_random_string, get_token_expiry_time_nanos, USER_PROFILE_INFO_DISPLAY_NAME,
USER_PROFILE_INFO_EMAIL, USER_PROFILE_INFO_IMAGE_URL,
};
use crate::oauth::AccessTokenContent;
use fidl_fuchsia_identity_external::{
Error as ApiError, OauthOpenIdConnectRequest, OauthOpenIdConnectRequestStream,
OpenIdTokenFromOauthRefreshTokenRequest, OpenIdUserInfoFromOauthAccessTokenRequest,
};
use fidl_fuchsia_identity_tokens::{OpenIdToken, OpenIdUserInfo};
use futures::future;
use futures::prelude::*;
use log::warn;
/// An implementation of the `OauthOpenIdConnect` protocol for testing.
pub struct OauthOpenIdConnect {}
impl OauthOpenIdConnect {
/// Handles requests received on the provided `request_stream`.
pub async fn handle_requests_for_stream(request_stream: OauthOpenIdConnectRequestStream) {
request_stream
.try_for_each(|r| future::ready(Self::handle_request(r)))
.unwrap_or_else(|e| warn!("Error running AuthProvider{:?}", e))
.await;
}
/// Handles a single `OauthOpenIdConnectRequest`.
fn handle_request(request: OauthOpenIdConnectRequest) -> Result<(), fidl::Error> {
match request {
OauthOpenIdConnectRequest::GetIdTokenFromRefreshToken { request, responder } => {
responder.send(&mut Self::get_id_token_from_refresh_token(request))
}
OauthOpenIdConnectRequest::GetUserInfoFromAccessToken { request, responder } => {
responder.send(&mut Self::get_user_info_from_access_token(request))
}
}
}
fn get_id_token_from_refresh_token(
request: OpenIdTokenFromOauthRefreshTokenRequest,
) -> Result<OpenIdToken, ApiError> {
let OpenIdTokenFromOauthRefreshTokenRequest { refresh_token, audiences: _ } = request;
let refresh_token = refresh_token.ok_or(ApiError::InvalidRequest)?;
let refresh_token_content = refresh_token.content.ok_or(ApiError::InvalidRequest)?;
Ok(OpenIdToken {
content: Some(format!("{}:idt_{}", refresh_token_content, generate_random_string())),
expiry_time: Some(get_token_expiry_time_nanos()),
})
}
fn get_user_info_from_access_token(
request: OpenIdUserInfoFromOauthAccessTokenRequest,
) -> Result<OpenIdUserInfo, ApiError> {
let access_token_content = request
.access_token
.ok_or(ApiError::InvalidRequest)?
.content
.ok_or(ApiError::InvalidRequest)?;
let subject = serde_json::from_str::<AccessTokenContent>(&access_token_content)
.map_err(|_| ApiError::InvalidRequest)?
.account_id;
Ok(OpenIdUserInfo {
subject: Some(subject),
name: Some(USER_PROFILE_INFO_DISPLAY_NAME.to_string()),
email: Some(USER_PROFILE_INFO_EMAIL.to_string()),
picture: Some(USER_PROFILE_INFO_IMAGE_URL.to_string()),
})
}
}
#[cfg(test)]
mod test {
use super::*;
use fidl::endpoints::create_proxy_and_stream;
use fidl_fuchsia_identity_external::{OauthOpenIdConnectMarker, OauthOpenIdConnectProxy};
use fidl_fuchsia_identity_tokens::{OauthAccessToken, OauthRefreshToken};
use fuchsia_async as fasync;
use futures::future::join;
async fn run_proxy_test<F, Fut>(test_fn: F)
where
F: FnOnce(OauthOpenIdConnectProxy) -> Fut,
Fut: Future<Output = Result<(), anyhow::Error>>,
{
let (proxy, stream) = create_proxy_and_stream::<OauthOpenIdConnectMarker>().unwrap();
let server_fut = OauthOpenIdConnect::handle_requests_for_stream(stream);
let (test_result, _) = join(test_fn(proxy), server_fut).await;
assert!(test_result.is_ok());
}
#[fasync::run_until_stalled(test)]
async fn get_id_token_from_refresh_token_test() {
run_proxy_test(|proxy| async move {
let refresh_token_content = format!("rt_{}", generate_random_string());
let refresh_token = OauthRefreshToken {
content: Some(refresh_token_content.clone()),
account_id: Some("account_id".to_string()),
};
let request = OpenIdTokenFromOauthRefreshTokenRequest {
refresh_token: Some(refresh_token),
audiences: None,
};
let id_token = proxy.get_id_token_from_refresh_token(request).await?.unwrap();
assert!(id_token.content.unwrap().contains(&refresh_token_content));
assert!(id_token.expiry_time.is_some());
Ok(())
})
.await;
}
#[fasync::run_until_stalled(test)]
async fn get_user_info_from_access_token_test() {
run_proxy_test(|proxy| async move {
let access_token_content = serde_json::to_string(&AccessTokenContent::new(
"rt_token".to_string(),
"test-account@example.com".to_string(),
None,
))
.unwrap();
let user_info = proxy
.get_user_info_from_access_token(OpenIdUserInfoFromOauthAccessTokenRequest {
access_token: Some(OauthAccessToken {
content: Some(access_token_content),
expiry_time: None,
}),
})
.await?
.unwrap();
assert_eq!(user_info.subject.unwrap(), "test-account@example.com".to_string());
assert!(user_info.name.is_some());
assert!(user_info.email.is_some());
assert!(user_info.picture.is_some());
Ok(())
})
.await;
}
}