use crate::{AuthorizationRequest, AuthorizationResponse, IdToken, Subject, Validator};
use anyhow::Result;
pub struct RelyingParty<V>
where
V: Validator + Subject,
{
validator: V,
}
impl<V> RelyingParty<V>
where
V: Validator + Subject,
{
pub fn new(validator: V) -> Self {
RelyingParty { validator }
}
pub async fn encode(&self, request: &AuthorizationRequest) -> Result<String> {
self.validator.encode(request).await
}
pub async fn validate_response(&self, response: &AuthorizationResponse) -> Result<IdToken> {
let token = response
.id_token()
.to_owned()
.ok_or(anyhow::anyhow!("No id_token parameter in response"))?;
let id_token: IdToken = self.validator.decode(token).await?;
Ok(id_token)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
claims::{Address, IndividualClaimRequest},
request::ResponseType,
scope::{Scope, ScopeValue},
test_utils::{MemoryStorage, MockSubject, Storage},
Provider, Registration, RequestUrl, StandardClaimsRequests, StandardClaimsValues,
};
use chrono::{Duration, Utc};
use lazy_static::lazy_static;
use wiremock::{
http::Method,
matchers::{method, path},
Mock, MockServer, ResponseTemplate,
};
lazy_static! {
pub static ref USER_CLAIMS: serde_json::Value = serde_json::json!(
{
"name": "Jane Doe",
"given_name": "Jane",
"family_name": "Doe",
"middle_name": "Middle",
"nickname": "JD",
"preferred_username": "j.doe",
"profile": "https://example.com/janedoe",
"picture": "https://example.com/janedoe/me.jpg",
"website": "https://example.com",
"email": "jane.doe@example.com",
"updated_at": 1311280970,
"phone_number": "+1 555 555 5555",
"address": {
"formatted": "100 Universal City Plaza\nHollywood, CA 91608",
"street_address": "100 Universal City Plaza",
"locality": "Hollywood",
"region": "CA",
"postal_code": "91608",
"country": "US"
}
}
);
}
#[tokio::test]
async fn test_relying_party() {
let mock_server = MockServer::start().await;
let server_url = mock_server.uri();
let validator = MockSubject::new("did:mock:1".to_string(), "key_identifier".to_string()).unwrap();
let relying_party = RelyingParty::new(validator);
let request: AuthorizationRequest = RequestUrl::builder()
.response_type(ResponseType::IdToken)
.client_id("did:mock:1".to_string())
.scope(Scope::from(vec![ScopeValue::OpenId, ScopeValue::Phone]))
.redirect_uri(format!("{server_url}/redirect_uri"))
.response_mode("post".to_string())
.registration(
Registration::default()
.with_subject_syntax_types_supported(vec!["did:mock".to_string()])
.with_id_token_signing_alg_values_supported(vec!["EdDSA".to_string()]),
)
.claims(
r#"{
"id_token": {
"name": null,
"email": {
"essential": true
},
"address": null,
"updated_at": null
}
}"#,
)
.exp((Utc::now() + Duration::minutes(10)).timestamp())
.nonce("n-0S6_WzA2Mj".to_string())
.build()
.and_then(TryInto::try_into)
.unwrap();
Mock::given(method("GET"))
.and(path("/request_uri"))
.respond_with(ResponseTemplate::new(200).set_body_string(relying_party.encode(&request).await.unwrap()))
.mount(&mock_server)
.await;
Mock::given(method("POST"))
.and(path("/redirect_uri"))
.respond_with(ResponseTemplate::new(200))
.mount(&mock_server)
.await;
let subject = MockSubject::new("did:mock:123".to_string(), "key_identifier".to_string()).unwrap();
let storage = MemoryStorage::new(serde_json::from_value(USER_CLAIMS.clone()).unwrap());
let provider = Provider::new(subject).await.unwrap();
let request_url = RequestUrl::builder()
.client_id("did:mock:1".to_string())
.request_uri(format!("{server_url}/request_uri"))
.build()
.unwrap();
let request = provider.validate_request(request_url).await.unwrap();
let request_claims = request.id_token_request_claims().unwrap();
assert_eq!(
request_claims,
StandardClaimsRequests {
name: Some(IndividualClaimRequest::Null),
email: Some(IndividualClaimRequest::object().essential(true)),
address: Some(IndividualClaimRequest::Null),
updated_at: Some(IndividualClaimRequest::Null),
phone_number: Some(IndividualClaimRequest::Null),
phone_number_verified: Some(IndividualClaimRequest::Null),
..Default::default()
}
);
let get_request = mock_server.received_requests().await.unwrap()[0].clone();
assert_eq!(get_request.method, Method::Get);
assert_eq!(get_request.url.path(), "/request_uri");
let response_claims = storage.fetch_claims(&request_claims);
let response = provider.generate_response(request, response_claims).await.unwrap();
provider.send_response(response).await.unwrap();
let post_request = mock_server.received_requests().await.unwrap()[1].clone();
assert_eq!(post_request.method, Method::Post);
assert_eq!(post_request.url.path(), "/redirect_uri");
let response: AuthorizationResponse = serde_urlencoded::from_bytes(post_request.body.as_slice()).unwrap();
let id_token = relying_party.validate_response(&response).await.unwrap();
assert_eq!(
id_token.standard_claims().to_owned(),
StandardClaimsValues {
name: Some("Jane Doe".to_string()),
email: Some("jane.doe@example.com".to_string()),
updated_at: Some(1311280970),
phone_number: Some("+1 555 555 5555".to_string()),
address: Some(Address {
formatted: Some("100 Universal City Plaza\nHollywood, CA 91608".to_string()),
street_address: Some("100 Universal City Plaza".to_string()),
locality: Some("Hollywood".to_string()),
region: Some("CA".to_string()),
postal_code: Some("91608".to_string()),
country: Some("US".to_string()),
}),
..Default::default()
}
);
}
}