Skip to content

Commit

Permalink
feat: convert FCM credential to string parameter
Browse files Browse the repository at this point in the history
FCM credentials are now specified as a JSON structure with an embedded
credential string. Please note that additional escaping may be required.
the original `auth_file` parameter is now deprecated.

As per above, please specify the FCM credential file content as an
included String. Additional string escapes may be required to properly
specify the inline JSON.

Closes #254
  • Loading branch information
jrconlin committed Feb 27, 2021
1 parent b4c5e5e commit 5ba885a
Show file tree
Hide file tree
Showing 6 changed files with 32 additions and 44 deletions.
1 change: 1 addition & 0 deletions autoendpoint/src/db/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ impl DbClientImpl {
}
}

#[allow(clippy::field_reassign_with_default)]
#[async_trait]
impl DbClient for DbClientImpl {
async fn add_user(&self, user: &DynamoDbUser) -> DbResult<()> {
Expand Down
1 change: 1 addition & 0 deletions autoendpoint/src/middleware/sentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn sentry_request_from_http(request: &ServiceRequest) -> sentry::protocol::Reque
}

/// Add request data to a Sentry event
#[allow(clippy::unnecessary_wraps)]
fn process_event(
mut event: Event<'static>,
request: &sentry::protocol::Request,
Expand Down
37 changes: 12 additions & 25 deletions autoendpoint/src/routers/fcm/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::time::Duration;
use url::Url;
use yup_oauth2::authenticator::DefaultAuthenticator;
use yup_oauth2::ServiceAccountAuthenticator;
use yup_oauth2::{ServiceAccountAuthenticator, ServiceAccountKey};

const OAUTH_SCOPES: &[&str] = &["https://www.googleapis.com/auth/firebase.messaging"];

Expand All @@ -29,7 +29,7 @@ impl FcmClient {
credential: FcmCredential,
http: reqwest::Client,
) -> std::io::Result<Self> {
let key_data = yup_oauth2::read_service_account_key(&credential.auth_file).await?;
let key_data = serde_json::from_str::<ServiceAccountKey>(&credential.credential)?;
let auth = ServiceAccountAuthenticator::builder(key_data)
.build()
.await?;
Expand Down Expand Up @@ -138,18 +138,15 @@ pub mod tests {
use crate::routers::fcm::settings::{FcmCredential, FcmSettings};
use crate::routers::RouterError;
use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf;
use tempfile::NamedTempFile;
use url::Url;

pub const PROJECT_ID: &str = "yup-test-243420";
const ACCESS_TOKEN: &str = "ya29.c.ElouBywiys0LyNaZoLPJcp1Fdi2KjFMxzvYKLXkTdvM-rDfqKlvEq6PiMhGoGHx97t5FAvz3eb_ahdwlBjSStxHtDVQB4ZPRJQ_EOi-iS7PnayahU2S9Jp8S6rk";

/// Write service data to a temporary file
pub fn make_service_file() -> NamedTempFile {
pub fn make_service_key() -> Vec<u8> {
// Taken from the yup-oauth2 tests
let contents = serde_json::json!({
serde_json::json!({
"type": "service_account",
"project_id": PROJECT_ID,
"private_key_id": "26de294916614a5ebdf7a065307ed3ea9941902b",
Expand All @@ -160,12 +157,7 @@ pub mod tests {
"token_uri": mockito::server_url() + "/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/yup-test-sa-1%40yup-test-243420.iam.gserviceaccount.com"
});

let mut file = tempfile::NamedTempFile::new().unwrap();
file.write_all(&serde_json::to_vec(&contents).unwrap())
.unwrap();
file
}).to_string().as_bytes().to_vec()
}

/// Mock the OAuth token endpoint to provide the access token
Expand All @@ -191,14 +183,14 @@ pub mod tests {
}

/// Make a FcmClient from the service auth data
async fn make_client(auth_file: PathBuf) -> FcmClient {
async fn make_client(credential: Vec<u8>) -> FcmClient {
FcmClient::new(
&FcmSettings {
base_url: Url::parse(&mockito::server_url()).unwrap(),
..Default::default()
},
FcmCredential {
auth_file,
credential: String::from_utf8(credential).unwrap(),
project_id: PROJECT_ID.to_string(),
},
reqwest::Client::new(),
Expand All @@ -211,8 +203,7 @@ pub mod tests {
/// expected FCM request.
#[tokio::test]
async fn sends_correct_request() {
let service_file = make_service_file();
let client = make_client(service_file.path().to_owned()).await;
let client = make_client(make_service_key()).await;
let _token_mock = mock_token_endpoint();
let fcm_mock = mock_fcm_endpoint_builder()
.match_header("Authorization", format!("Bearer {}", ACCESS_TOKEN).as_str())
Expand All @@ -231,8 +222,7 @@ pub mod tests {
/// Authorization errors are handled
#[tokio::test]
async fn unauthorized() {
let service_file = make_service_file();
let client = make_client(service_file.path().to_owned()).await;
let client = make_client(make_service_key()).await;
let _token_mock = mock_token_endpoint();
let _fcm_mock = mock_fcm_endpoint_builder()
.with_status(401)
Expand All @@ -253,8 +243,7 @@ pub mod tests {
/// 404 errors are handled
#[tokio::test]
async fn not_found() {
let service_file = make_service_file();
let client = make_client(service_file.path().to_owned()).await;
let client = make_client(make_service_key()).await;
let _token_mock = mock_token_endpoint();
let _fcm_mock = mock_fcm_endpoint_builder()
.with_status(404)
Expand All @@ -275,8 +264,7 @@ pub mod tests {
/// Unhandled errors (where an error object is returned) are wrapped and returned
#[tokio::test]
async fn other_fcm_error() {
let service_file = make_service_file();
let client = make_client(service_file.path().to_owned()).await;
let client = make_client(make_service_key()).await;
let _token_mock = mock_token_endpoint();
let _fcm_mock = mock_fcm_endpoint_builder()
.with_status(400)
Expand All @@ -301,8 +289,7 @@ pub mod tests {
/// Unknown errors (where an error object is NOT returned) is handled
#[tokio::test]
async fn unknown_fcm_error() {
let service_file = make_service_file();
let client = make_client(service_file.path().to_owned()).await;
let client = make_client(make_service_key()).await;
let _token_mock = mock_token_endpoint();
let _fcm_mock = mock_fcm_endpoint_builder()
.with_status(400)
Expand Down
31 changes: 15 additions & 16 deletions autoendpoint/src/routers/fcm/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,31 +150,33 @@ mod tests {
use crate::extractors::routers::RouterType;
use crate::routers::common::tests::{make_notification, CHANNEL_ID};
use crate::routers::fcm::client::tests::{
make_service_file, mock_fcm_endpoint_builder, mock_token_endpoint, PROJECT_ID,
make_service_key, mock_fcm_endpoint_builder, mock_token_endpoint, PROJECT_ID,
};
use crate::routers::fcm::error::FcmError;
use crate::routers::fcm::router::FcmRouter;
use crate::routers::fcm::settings::FcmSettings;
use crate::routers::RouterError;
use crate::routers::{Router, RouterResponse};

use cadence::StatsdClient;
use mockall::predicate;
use std::collections::HashMap;
use std::path::PathBuf;
use url::Url;

const FCM_TOKEN: &str = "test-token";

/// Create a router for testing, using the given service auth file
async fn make_router(auth_file: PathBuf, ddb: Box<dyn DbClient>) -> FcmRouter {
async fn make_router(credential: String, ddb: Box<dyn DbClient>) -> FcmRouter {
FcmRouter::new(
FcmSettings {
base_url: Url::parse(&mockito::server_url()).unwrap(),
credentials: format!(
r#"{{ "dev": {{ "project_id": "{}", "auth_file": "{}" }} }}"#,
PROJECT_ID,
auth_file.to_string_lossy()
),
credentials: serde_json::json!({
"dev": {
"project_id": PROJECT_ID,
"credential": credential
}
})
.to_string(),
..Default::default()
},
Url::parse("http://localhost:8080/").unwrap(),
Expand All @@ -200,9 +202,8 @@ mod tests {
/// A notification with no data is sent to FCM
#[tokio::test]
async fn successful_routing_no_data() {
let service_file = make_service_file();
let ddb = MockDbClient::new().into_boxed_arc();
let router = make_router(service_file.path().to_owned(), ddb).await;
let router = make_router(String::from_utf8(make_service_key()).unwrap(), ddb).await;
let _token_mock = mock_token_endpoint();
let fcm_mock = mock_fcm_endpoint_builder()
.match_body(
Expand Down Expand Up @@ -235,9 +236,8 @@ mod tests {
/// A notification with data is sent to FCM
#[tokio::test]
async fn successful_routing_with_data() {
let service_file = make_service_file();
let ddb = MockDbClient::new().into_boxed_arc();
let router = make_router(service_file.path().to_owned(), ddb).await;
let router = make_router(String::from_utf8(make_service_key()).unwrap(), ddb).await;
let _token_mock = mock_token_endpoint();
let fcm_mock = mock_fcm_endpoint_builder()
.match_body(
Expand Down Expand Up @@ -277,9 +277,8 @@ mod tests {
/// the FCM request is not sent.
#[tokio::test]
async fn missing_client() {
let service_file = make_service_file();
let ddb = MockDbClient::new().into_boxed_arc();
let router = make_router(service_file.path().to_owned(), ddb).await;
let router = make_router(String::from_utf8(make_service_key()).unwrap(), ddb).await;
let _token_mock = mock_token_endpoint();
let fcm_mock = mock_fcm_endpoint_builder().expect(0).create();
let mut router_data = default_router_data();
Expand Down Expand Up @@ -312,8 +311,8 @@ mod tests {
.times(1)
.return_once(|_| Ok(()));

let service_file = make_service_file();
let router = make_router(service_file.path().to_owned(), ddb.into_boxed_arc()).await;
let key = String::from_utf8(make_service_key()).unwrap();
let router = make_router(key, ddb.into_boxed_arc()).await;
let _token_mock = mock_token_endpoint();
let _fcm_mock = mock_fcm_endpoint_builder()
.with_status(404)
Expand Down
4 changes: 2 additions & 2 deletions autoendpoint/src/routers/fcm/settings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::collections::HashMap;
use std::path::PathBuf;

use url::Url;

/// Settings for `FcmRouter`
Expand All @@ -24,7 +24,7 @@ pub struct FcmSettings {
#[derive(Clone, Debug, serde::Deserialize)]
pub struct FcmCredential {
pub project_id: String,
pub auth_file: PathBuf,
pub credential: String,
}

impl Default for FcmSettings {
Expand Down
2 changes: 1 addition & 1 deletion configs/autoendpoint.toml.sample
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
#credentials = """{
# "test": {
# "project_id": "autoendpoint-test",
# "auth_file": "autoendpoint-test-credentials.json"
# "credential": "{\"type\":\"service_account\",...
# }
#}"""

Expand Down

0 comments on commit 5ba885a

Please sign in to comment.