openid-client 0.2.7

OpenID client for Rust
Documentation
use josekit::{jws::JwsHeader, jwt::JwtPayload};
use serde_json::json;

use crate::{
    client::Client,
    helpers::now,
    http_client::DefaultHttpClient,
    issuer::Issuer,
    tokenset::{TokenSet, TokenSetParams},
    types::{ClientMetadata, HttpMethod, IssuerMetadata},
};

use crate::tests::test_http_client::TestHttpReqRes;

fn get_client() -> Client {
    let issuer_metadata = IssuerMetadata {
        issuer: "https://op.example.com".to_string(),
        token_endpoint: Some("https://op.example.com/token".to_string()),
        ..Default::default()
    };
    let issuer = Issuer::new(issuer_metadata);

    let client_metadata = ClientMetadata {
        client_id: Some("identifier".to_string()),
        client_secret: Some("larger_than_32_char_client_secret".to_string()),
        id_token_signed_response_alg: Some("HS256".to_string()),
        ..Default::default()
    };

    issuer.client(client_metadata, None, None, None).unwrap()
}

#[tokio::test]
async fn rejects_when_passed_a_token_set_not_containing_refresh_token() {
    let err = get_client()
        .refresh_async(&DefaultHttpClient, TokenSet::default(), None)
        .await
        .unwrap_err();

    assert!(err.is_type_error());
    assert_eq!(
        "refresh_token not present in TokenSet",
        err.type_error().error.message
    );
}

#[tokio::test]
async fn does_a_refresh_token_grant_with_refresh_token() {
    let http_client = TestHttpReqRes::new("https://op.example.com/token")
        .assert_request_method(HttpMethod::POST)
        .assert_request_header("accept", vec!["application/json".to_string()])
        .assert_request_header(
            "authorization",
            vec!["Basic aWRlbnRpZmllcjpsYXJnZXJfdGhhbl8zMl9jaGFyX2NsaWVudF9zZWNyZXQ=".to_string()],
        )
        .assert_request_header("content-length", vec!["51".to_string()])
        .assert_request_header(
            "content-type",
            vec!["application/x-www-form-urlencoded".to_string()],
        )
        .assert_request_body("refresh_token=refreshValue&grant_type=refresh_token")
        .set_response_body("{}")
        .build();

    let token_set_params = TokenSetParams {
        refresh_token: Some("refreshValue".to_string()),
        ..Default::default()
    };

    let _ = get_client()
        .refresh_async(&http_client, TokenSet::new(token_set_params), None)
        .await;

    http_client.assert();
}

#[tokio::test]
async fn returns_a_token_set() {
    let http_client = TestHttpReqRes::new("https://op.example.com/token")
        .assert_request_method(HttpMethod::POST)
        .assert_request_header("accept", vec!["application/json".to_string()])
        .assert_request_header(
            "authorization",
            vec!["Basic aWRlbnRpZmllcjpsYXJnZXJfdGhhbl8zMl9jaGFyX2NsaWVudF9zZWNyZXQ=".to_string()],
        )
        .assert_request_header("content-length", vec!["51".to_string()])
        .assert_request_header(
            "content-type",
            vec!["application/x-www-form-urlencoded".to_string()],
        )
        .assert_request_body("refresh_token=refreshValue&grant_type=refresh_token")
        .set_response_content_type_header("application/json")
        .set_response_body(r#"{"access_token":"tokenValue"}"#)
        .build();

    let token_set_params = TokenSetParams {
        refresh_token: Some("refreshValue".to_string()),
        ..Default::default()
    };

    let token_set = get_client()
        .refresh_async(&http_client, TokenSet::new(token_set_params), None)
        .await
        .unwrap();

    assert_eq!("tokenValue", token_set.get_access_token().unwrap());
}

#[tokio::test]
async fn passes_id_token_validations_when_id_token_is_returned() {
    let mut client = get_client();

    let mut payload = JwtPayload::new();
    payload
        .set_claim(
            "iss",
            Some(json!(client.issuer.as_ref().unwrap().issuer.clone())),
        )
        .unwrap();
    let iat = now();
    let exp = iat + 300;

    payload.set_claim("iat", Some(json!(iat))).unwrap();
    payload.set_claim("exp", Some(json!(exp))).unwrap();
    payload.set_claim("aud", Some(json!("identifier"))).unwrap();
    payload.set_claim("sub", Some(json!("foo"))).unwrap();

    let mut header = JwsHeader::new();
    header.set_claim("alg", Some(json!("HS256"))).unwrap();

    let signer = josekit::jws::HS256
        .signer_from_bytes("larger_than_32_char_client_secret")
        .unwrap();

    let id_token = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap();

    let http_client = TestHttpReqRes::new("https://op.example.com/token")
        .assert_request_method(HttpMethod::POST)
        .assert_request_header("accept", vec!["application/json".to_string()])
        .assert_request_header(
            "authorization",
            vec!["Basic aWRlbnRpZmllcjpsYXJnZXJfdGhhbl8zMl9jaGFyX2NsaWVudF9zZWNyZXQ=".to_string()],
        )
        .assert_request_header("content-length", vec!["51".to_string()])
        .assert_request_header(
            "content-type",
            vec!["application/x-www-form-urlencoded".to_string()],
        )
        .assert_request_body("refresh_token=refreshValue&grant_type=refresh_token")
        .set_response_content_type_header("application/json")
        .set_response_body(format!(
            r#"{{"access_token":"present","refresh_token":"refreshValue","id_token":"{}"}}"#,
            &id_token
        ))
        .build();

    payload.set_claim("exp", Some(json!(exp + 60))).unwrap();

    let id_token_old = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap();

    let token_set_params = TokenSetParams {
        refresh_token: Some("refreshValue".to_string()),
        access_token: Some("present".to_string()),
        id_token: Some(id_token_old),
        ..Default::default()
    };

    let token_set = client
        .refresh_async(&http_client, TokenSet::new(token_set_params), None)
        .await
        .unwrap();

    assert_eq!(id_token, token_set.get_id_token().unwrap());
}

#[tokio::test]
async fn rejects_when_returned_id_token_sub_does_not_match_the_one_passed_in() {
    let mut client = get_client();

    let mut payload = JwtPayload::new();
    payload
        .set_claim(
            "iss",
            Some(json!(client.issuer.as_ref().unwrap().issuer.clone())),
        )
        .unwrap();
    let iat = now();
    let exp = iat + 300;

    payload.set_claim("iat", Some(json!(iat))).unwrap();
    payload.set_claim("exp", Some(json!(exp))).unwrap();
    payload.set_claim("aud", Some(json!("identifier"))).unwrap();
    payload.set_claim("sub", Some(json!("bar"))).unwrap();

    let mut header = JwsHeader::new();
    header.set_claim("alg", Some(json!("HS256"))).unwrap();

    let signer = josekit::jws::HS256
        .signer_from_bytes("larger_than_32_char_client_secret")
        .unwrap();

    let id_token = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap();

    let http_client = TestHttpReqRes::new("https://op.example.com/token")
        .assert_request_method(HttpMethod::POST)
        .assert_request_header("accept", vec!["application/json".to_string()])
        .assert_request_header(
            "authorization",
            vec!["Basic aWRlbnRpZmllcjpsYXJnZXJfdGhhbl8zMl9jaGFyX2NsaWVudF9zZWNyZXQ=".to_string()],
        )
        .assert_request_header("content-length", vec!["51".to_string()])
        .assert_request_header(
            "content-type",
            vec!["application/x-www-form-urlencoded".to_string()],
        )
        .assert_request_body("refresh_token=refreshValue&grant_type=refresh_token")
        .set_response_content_type_header("application/json")
        .set_response_body(format!(
            r#"{{"access_token":"present","refresh_token":"refreshValue","id_token":"{}"}}"#,
            &id_token
        ))
        .build();

    payload.set_claim("sub", Some(json!("foo"))).unwrap();

    let id_token_old = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap();

    let token_set_params = TokenSetParams {
        refresh_token: Some("refreshValue".to_string()),
        access_token: Some("present".to_string()),
        id_token: Some(id_token_old),
        ..Default::default()
    };

    let err = client
        .refresh_async(&http_client, TokenSet::new(token_set_params), None)
        .await
        .unwrap_err();

    assert!(err.is_rp_error());
    assert_eq!(
        "sub mismatch, expected foo, got: bar",
        err.rp_error().error.message
    );
}