#![deny(warnings)]
use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryFrom;
use std::str::FromStr;

use compact_jwt::{JwkKeySet, JwsEs256Verifier, JwsVerifier, OidcToken, OidcUnverified};
use kanidm_proto::constants::uri::{OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_PERMIT};
use kanidm_proto::constants::*;
use kanidm_proto::internal::Oauth2ClaimMapJoin;
use kanidm_proto::oauth2::{
    AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
    AccessTokenResponse, AccessTokenType, AuthorisationResponse, GrantTypeReq,
    OidcDiscoveryResponse,
};
use kanidmd_lib::prelude::{Attribute, IDM_ALL_ACCOUNTS};
use oauth2_ext::PkceCodeChallenge;
use reqwest::header::{HeaderValue, CONTENT_TYPE};
use reqwest::StatusCode;
use uri::{OAUTH2_TOKEN_ENDPOINT, OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
use url::Url;

use kanidm_client::KanidmClient;
use kanidmd_testkit::{
    assert_no_cache, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER, NOT_ADMIN_TEST_EMAIL,
    NOT_ADMIN_TEST_PASSWORD, NOT_ADMIN_TEST_USERNAME, TEST_INTEGRATION_RS_DISPLAY,
    TEST_INTEGRATION_RS_GROUP_ALL, TEST_INTEGRATION_RS_ID, TEST_INTEGRATION_RS_REDIRECT_URL,
    TEST_INTEGRATION_RS_URL,
};

#[kanidmd_testkit::test]
async fn test_oauth2_openid_basic_flow(rsclient: KanidmClient) {
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    // Create an oauth2 application integration.
    rsclient
        .idm_oauth2_rs_basic_create(
            TEST_INTEGRATION_RS_ID,
            TEST_INTEGRATION_RS_DISPLAY,
            TEST_INTEGRATION_RS_URL,
        )
        .await
        .expect("Failed to create oauth2 config");

    rsclient
        .idm_oauth2_client_add_origin(
            TEST_INTEGRATION_RS_ID,
            &Url::parse(TEST_INTEGRATION_RS_REDIRECT_URL).expect("Invalid URL"),
        )
        .await
        .expect("Failed to update oauth2 config");

    // Extend the admin account with extended details for openid claims.
    rsclient
        .idm_person_account_create(NOT_ADMIN_TEST_USERNAME, NOT_ADMIN_TEST_USERNAME)
        .await
        .expect("Failed to create account details");

    rsclient
        .idm_person_account_set_attr(
            NOT_ADMIN_TEST_USERNAME,
            Attribute::Mail.as_ref(),
            &[NOT_ADMIN_TEST_EMAIL],
        )
        .await
        .expect("Failed to create account mail");

    rsclient
        .idm_person_account_primary_credential_set_password(
            NOT_ADMIN_TEST_USERNAME,
            NOT_ADMIN_TEST_PASSWORD,
        )
        .await
        .expect("Failed to configure account password");

    rsclient
        .idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true, true, true)
        .await
        .expect("Failed to update oauth2 config");

    rsclient
        .idm_oauth2_rs_update_scope_map(
            TEST_INTEGRATION_RS_ID,
            IDM_ALL_ACCOUNTS.name,
            vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    rsclient
        .idm_oauth2_rs_update_sup_scope_map(
            TEST_INTEGRATION_RS_ID,
            IDM_ALL_ACCOUNTS.name,
            vec![ADMIN_TEST_USER],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    let client_secret = rsclient
        .idm_oauth2_rs_get_basic_secret(TEST_INTEGRATION_RS_ID)
        .await
        .ok()
        .flatten()
        .expect("Failed to retrieve test_integration basic secret");

    // Get our admin's auth token for our new client.
    // We have to re-auth to update the mail field.
    let res = rsclient
        .auth_simple_password(NOT_ADMIN_TEST_USERNAME, NOT_ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());
    let oauth_test_uat = rsclient
        .get_token()
        .await
        .expect("No user auth token found");

    // We need a new reqwest client here.

    // from here, we can now begin what would be a "interaction" to the oauth server.
    // Create a new reqwest client - we'll be using this manually.
    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .no_proxy()
        .build()
        .expect("Failed to create client.");

    // Step 0 - get the openid discovery details and the public key.
    let response = client
        .request(
            reqwest::Method::OPTIONS,
            rsclient.make_url("/oauth2/openid/test_integration/.well-known/openid-configuration"),
        )
        .send()
        .await
        .expect("Failed to send discovery preflight request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    let cors_header: &str = response
        .headers()
        .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
        .expect("missing access-control-allow-origin header")
        .to_str()
        .expect("invalid access-control-allow-origin header");
    assert!(cors_header.eq("*"));

    let response = client
        .get(rsclient.make_url("/oauth2/openid/test_integration/.well-known/openid-configuration"))
        .send()
        .await
        .expect("Failed to send request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    // Assert CORS on the GET too.
    let cors_header: &str = response
        .headers()
        .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
        .expect("missing access-control-allow-origin header")
        .to_str()
        .expect("invalid access-control-allow-origin header");
    assert!(cors_header.eq("*"));

    assert_no_cache!(response);

    let discovery: OidcDiscoveryResponse = response
        .json()
        .await
        .expect("Failed to access response body");

    tracing::trace!(?discovery);

    // Most values are checked in idm/oauth2.rs, but we want to sanity check
    // the urls here as an extended function smoke test.
    assert_eq!(
        discovery.issuer,
        rsclient.make_url("/oauth2/openid/test_integration")
    );

    assert_eq!(
        discovery.authorization_endpoint,
        rsclient.make_url("/ui/oauth2")
    );

    assert_eq!(
        discovery.token_endpoint,
        rsclient.make_url(OAUTH2_TOKEN_ENDPOINT)
    );

    assert!(
        discovery.userinfo_endpoint
            == Some(rsclient.make_url("/oauth2/openid/test_integration/userinfo"))
    );

    assert!(
        discovery.jwks_uri == rsclient.make_url("/oauth2/openid/test_integration/public_key.jwk")
    );

    // Step 0 - get the jwks public key.
    let response = client
        .get(rsclient.make_url("/oauth2/openid/test_integration/public_key.jwk"))
        .send()
        .await
        .expect("Failed to send request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    assert_no_cache!(response);

    let mut jwk_set: JwkKeySet = response
        .json()
        .await
        .expect("Failed to access response body");

    let public_jwk = jwk_set.keys.pop().expect("No public key in set!");

    let jws_validator = JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");

    // Step 1 - the Oauth2 Resource Server would send a redirect to the authorisation
    // server, where the url contains a series of authorisation request parameters.
    //
    // Since we are a client, we can just "pretend" we got the redirect, and issue the
    // get call directly. This should be a 200. (?)

    let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();

    let response = client
        .get(rsclient.make_url(OAUTH2_AUTHORISE))
        .bearer_auth(oauth_test_uat.clone())
        .query(&[
            ("response_type", "code"),
            ("client_id", TEST_INTEGRATION_RS_ID),
            ("state", "YWJjZGVm"),
            ("code_challenge", pkce_code_challenge.as_str()),
            ("code_challenge_method", "S256"),
            ("redirect_uri", TEST_INTEGRATION_RS_REDIRECT_URL),
            ("scope", "email read openid"),
        ])
        .send()
        .await
        .expect("Failed to send request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    assert_no_cache!(response);

    let consent_req: AuthorisationResponse = response
        .json()
        .await
        .expect("Failed to access response body");

    let consent_token = if let AuthorisationResponse::ConsentRequested {
        consent_token,
        scopes,
        ..
    } = consent_req
    {
        // Note the supplemental scope here (admin)
        dbg!(&scopes);
        assert!(scopes.contains("admin"));
        consent_token
    } else {
        unreachable!();
    };

    // Step 2 - we now send the consent get to the server which yields a redirect with a
    // state and code.

    let response = client
        .get(rsclient.make_url(OAUTH2_AUTHORISE_PERMIT))
        .bearer_auth(oauth_test_uat)
        .query(&[("token", consent_token.as_str())])
        .send()
        .await
        .expect("Failed to send request.");

    // This should yield a 302 redirect with some query params.
    assert_eq!(response.status(), reqwest::StatusCode::FOUND);
    assert_no_cache!(response);

    // And we should have a URL in the location header.
    let redir_str = response
        .headers()
        .get("Location")
        .and_then(|hv| hv.to_str().ok().map(str::to_string))
        .expect("Invalid redirect url");

    // Now check it's content
    let redir_url = Url::parse(&redir_str).expect("Url parse failure");

    // We should have state and code.
    let pairs: BTreeMap<_, _> = redir_url.query_pairs().collect();

    let code = pairs.get("code").expect("code not found!");

    let state = pairs.get("state").expect("state not found!");

    assert_eq!(state, "YWJjZGVm");

    // Step 3 - the "resource server" then uses this state and code to directly contact
    // the authorisation server to request a token.

    let form_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
        code: code.to_string(),
        redirect_uri: Url::parse(TEST_INTEGRATION_RS_REDIRECT_URL).expect("Invalid URL"),
        code_verifier: Some(pkce_code_verifier.secret().clone()),
    }
    .into();

    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_ENDPOINT))
        .basic_auth(TEST_INTEGRATION_RS_ID, Some(client_secret.clone()))
        .form(&form_req)
        .send()
        .await
        .expect("Failed to send code exchange request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    let cors_header: &str = response
        .headers()
        .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
        .expect("missing access-control-allow-origin header")
        .to_str()
        .expect("invalid access-control-allow-origin header");
    assert!(cors_header.eq("*"));

    assert!(
        response.headers().get(CONTENT_TYPE) == Some(&HeaderValue::from_static(APPLICATION_JSON))
    );
    assert_no_cache!(response);

    // The body is a json AccessTokenResponse

    let atr = response
        .json::<AccessTokenResponse>()
        .await
        .expect("Unable to decode AccessTokenResponse");

    // Step 4 - inspect the granted token.
    let intr_request = AccessTokenIntrospectRequest {
        token: atr.access_token.clone(),
        token_type_hint: None,
    };

    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_INTROSPECT_ENDPOINT))
        .basic_auth(TEST_INTEGRATION_RS_ID, Some(client_secret.clone()))
        .form(&intr_request)
        .send()
        .await
        .expect("Failed to send token introspect request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    tracing::trace!("{:?}", response.headers());
    assert!(
        response.headers().get(CONTENT_TYPE) == Some(&HeaderValue::from_static(APPLICATION_JSON))
    );
    assert_no_cache!(response);

    let tir = response
        .json::<AccessTokenIntrospectResponse>()
        .await
        .expect("Unable to decode AccessTokenIntrospectResponse");

    assert!(tir.active);
    assert!(tir.scope.is_some());
    assert_eq!(tir.client_id.as_deref(), Some(TEST_INTEGRATION_RS_ID));
    assert_eq!(
        tir.username.as_deref(),
        Some(format!("{}@localhost", NOT_ADMIN_TEST_USERNAME).as_str())
    );
    assert_eq!(tir.token_type, Some(AccessTokenType::Bearer));
    assert!(tir.exp.is_some());
    assert!(tir.iat.is_some());
    assert!(tir.nbf.is_some());
    assert!(tir.sub.is_some());
    assert_eq!(tir.aud.as_deref(), Some(TEST_INTEGRATION_RS_ID));
    assert!(tir.iss.is_none());
    assert!(tir.jti.is_none());

    // Step 5 - check that the id_token (openid) matches the userinfo endpoint.
    let oidc_unverified =
        OidcUnverified::from_str(atr.id_token.as_ref().unwrap()).expect("Failed to parse id_token");

    let oidc = jws_validator
        .verify(&oidc_unverified)
        .expect("Failed to verify oidc")
        .verify_exp(0)
        .expect("Failed to check exp");

    // This is mostly checked inside of idm/oauth2.rs. This is more to check the oidc
    // token and the userinfo endpoints.
    assert_eq!(
        oidc.iss,
        rsclient.make_url("/oauth2/openid/test_integration")
    );
    eprintln!("{:?}", oidc.s_claims.email);
    assert_eq!(oidc.s_claims.email.as_deref(), Some(NOT_ADMIN_TEST_EMAIL));
    assert_eq!(oidc.s_claims.email_verified, Some(true));

    let response = client
        .get(rsclient.make_url("/oauth2/openid/test_integration/userinfo"))
        .bearer_auth(atr.access_token.clone())
        .send()
        .await
        .expect("Failed to send userinfo request.");

    tracing::trace!("{:?}", response.headers());
    assert!(
        response.headers().get(CONTENT_TYPE) == Some(&HeaderValue::from_static(APPLICATION_JSON))
    );
    let userinfo = response
        .json::<OidcToken>()
        .await
        .expect("Unable to decode OidcToken from userinfo");

    eprintln!("userinfo {userinfo:?}");
    eprintln!("oidc {oidc:?}");

    assert_eq!(userinfo, oidc);

    // Step 6 - Show that our client can perform a client credentials grant

    let form_req: AccessTokenRequest = GrantTypeReq::ClientCredentials {
        scope: Some(BTreeSet::from([
            "email".to_string(),
            "read".to_string(),
            "openid".to_string(),
        ])),
    }
    .into();

    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_ENDPOINT))
        .basic_auth(TEST_INTEGRATION_RS_ID, Some(client_secret.clone()))
        .form(&form_req)
        .send()
        .await
        .expect("Failed to send client credentials request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    let atr = response
        .json::<AccessTokenResponse>()
        .await
        .expect("Unable to decode AccessTokenResponse");

    // Step 7 - inspect the granted client credentials token.
    let intr_request = AccessTokenIntrospectRequest {
        token: atr.access_token.clone(),
        token_type_hint: None,
    };

    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_INTROSPECT_ENDPOINT))
        .basic_auth(TEST_INTEGRATION_RS_ID, Some(client_secret))
        .form(&intr_request)
        .send()
        .await
        .expect("Failed to send token introspect request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    let tir = response
        .json::<AccessTokenIntrospectResponse>()
        .await
        .expect("Unable to decode AccessTokenIntrospectResponse");

    assert!(tir.active);
    assert!(tir.scope.is_some());
    assert_eq!(tir.client_id.as_deref(), Some(TEST_INTEGRATION_RS_ID));
    assert_eq!(tir.username.as_deref(), Some("test_integration@localhost"));
    assert_eq!(tir.token_type, Some(AccessTokenType::Bearer));

    // auth back with admin so we can test deleting things
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());
    rsclient
        .idm_oauth2_rs_delete_sup_scope_map(TEST_INTEGRATION_RS_ID, TEST_INTEGRATION_RS_GROUP_ALL)
        .await
        .expect("Failed to update oauth2 scopes");
}

#[kanidmd_testkit::test]
async fn test_oauth2_openid_public_flow(rsclient: KanidmClient) {
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    // Create an oauth2 application integration.
    rsclient
        .idm_oauth2_rs_public_create(
            TEST_INTEGRATION_RS_ID,
            TEST_INTEGRATION_RS_DISPLAY,
            TEST_INTEGRATION_RS_URL,
        )
        .await
        .expect("Failed to create oauth2 config");

    rsclient
        .idm_oauth2_client_add_origin(
            TEST_INTEGRATION_RS_ID,
            &Url::parse(TEST_INTEGRATION_RS_REDIRECT_URL).expect("Invalid URL"),
        )
        .await
        .expect("Failed to update oauth2 config");

    // Extend the admin account with extended details for openid claims.
    rsclient
        .idm_person_account_create(NOT_ADMIN_TEST_USERNAME, NOT_ADMIN_TEST_USERNAME)
        .await
        .expect("Failed to create account details");

    rsclient
        .idm_person_account_set_attr(
            NOT_ADMIN_TEST_USERNAME,
            Attribute::Mail.as_ref(),
            &[NOT_ADMIN_TEST_EMAIL],
        )
        .await
        .expect("Failed to create account mail");

    rsclient
        .idm_person_account_primary_credential_set_password(
            NOT_ADMIN_TEST_USERNAME,
            ADMIN_TEST_PASSWORD,
        )
        .await
        .expect("Failed to configure account password");

    rsclient
        .idm_oauth2_rs_update(TEST_INTEGRATION_RS_ID, None, None, None, true, true, true)
        .await
        .expect("Failed to update oauth2 config");

    rsclient
        .idm_oauth2_rs_update_scope_map(
            TEST_INTEGRATION_RS_ID,
            IDM_ALL_ACCOUNTS.name,
            vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    rsclient
        .idm_oauth2_rs_update_sup_scope_map(
            TEST_INTEGRATION_RS_ID,
            IDM_ALL_ACCOUNTS.name,
            vec![ADMIN_TEST_USER],
        )
        .await
        .expect("Failed to update oauth2 scopes");

    // Add a custom claim map.
    rsclient
        .idm_oauth2_rs_update_claim_map(
            TEST_INTEGRATION_RS_ID,
            "test_claim",
            IDM_ALL_ACCOUNTS.name,
            &["claim_a".to_string(), "claim_b".to_string()],
        )
        .await
        .expect("Failed to update oauth2 claims");

    // Set an alternate join
    rsclient
        .idm_oauth2_rs_update_claim_map_join(
            TEST_INTEGRATION_RS_ID,
            "test_claim",
            Oauth2ClaimMapJoin::Ssv,
        )
        .await
        .expect("Failed to update oauth2 claims");

    // Get our admin's auth token for our new client.
    // We have to re-auth to update the mail field.
    let res = rsclient
        .auth_simple_password(NOT_ADMIN_TEST_USERNAME, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());
    let oauth_test_uat = rsclient
        .get_token()
        .await
        .expect("No user auth token found");

    // We need a new reqwest client here.

    // from here, we can now begin what would be a "interaction" to the oauth server.
    // Create a new reqwest client - we'll be using this manually.
    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .no_proxy()
        .build()
        .expect("Failed to create client.");

    // Step 0 - get the jwks public key.
    let response = client
        .get(rsclient.make_url("/oauth2/openid/test_integration/public_key.jwk"))
        .send()
        .await
        .expect("Failed to send request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    assert_no_cache!(response);

    let mut jwk_set: JwkKeySet = response
        .json()
        .await
        .expect("Failed to access response body");

    let public_jwk = jwk_set.keys.pop().expect("No public key in set!");

    let jws_validator = JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");

    // Step 1 - the Oauth2 Resource Server would send a redirect to the authorisation
    // server, where the url contains a series of authorisation request parameters.
    //
    // Since we are a client, we can just "pretend" we got the redirect, and issue the
    // get call directly. This should be a 200. (?)
    let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256();

    let response = client
        .get(rsclient.make_url(OAUTH2_AUTHORISE))
        .bearer_auth(oauth_test_uat.clone())
        .query(&[
            ("response_type", "code"),
            ("client_id", TEST_INTEGRATION_RS_ID),
            ("state", "YWJjZGVm"),
            ("code_challenge", pkce_code_challenge.as_str()),
            ("code_challenge_method", "S256"),
            ("redirect_uri", TEST_INTEGRATION_RS_REDIRECT_URL),
            ("scope", "email read openid"),
        ])
        .send()
        .await
        .expect("Failed to send request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    assert_no_cache!(response);

    let consent_req: AuthorisationResponse = response
        .json()
        .await
        .expect("Failed to access response body");

    let consent_token = if let AuthorisationResponse::ConsentRequested {
        consent_token,
        scopes,
        ..
    } = consent_req
    {
        // Note the supplemental scope here (admin)
        assert!(scopes.contains(ADMIN_TEST_USER));
        consent_token
    } else {
        unreachable!();
    };

    // Step 2 - we now send the consent get to the server which yields a redirect with a
    // state and code.
    let response = client
        .get(rsclient.make_url(OAUTH2_AUTHORISE_PERMIT))
        .bearer_auth(oauth_test_uat)
        .query(&[("token", consent_token.as_str())])
        .send()
        .await
        .expect("Failed to send request.");

    // This should yield a 302 redirect with some query params.
    assert_eq!(response.status(), reqwest::StatusCode::FOUND);
    assert_no_cache!(response);

    // And we should have a URL in the location header.
    let redir_str = response
        .headers()
        .get("Location")
        .and_then(|hv| hv.to_str().ok().map(str::to_string))
        .expect("Invalid redirect url");

    // Now check it's content
    let redir_url = Url::parse(&redir_str).expect("Url parse failure");

    // We should have state and code.
    let pairs: BTreeMap<_, _> = redir_url.query_pairs().collect();

    let code = pairs.get("code").expect("code not found!");

    let state = pairs.get("state").expect("state not found!");

    assert_eq!(state, "YWJjZGVm");

    // Step 3 - the "resource server" then uses this state and code to directly contact
    // the authorisation server to request a token.

    let form_req = AccessTokenRequest {
        grant_type: GrantTypeReq::AuthorizationCode {
            code: code.to_string(),
            redirect_uri: Url::parse(TEST_INTEGRATION_RS_REDIRECT_URL).expect("Invalid URL"),
            code_verifier: Some(pkce_code_verifier.secret().clone()),
        },
        client_id: Some(TEST_INTEGRATION_RS_ID.to_string()),
        client_secret: None,
    };

    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_ENDPOINT))
        .form(&form_req)
        .send()
        .await
        .expect("Failed to send code exchange request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    assert_no_cache!(response);

    // The body is a json AccessTokenResponse
    let atr = response
        .json::<AccessTokenResponse>()
        .await
        .expect("Unable to decode AccessTokenResponse");

    // Step 5 - check that the id_token (openid) matches the userinfo endpoint.
    let oidc_unverified =
        OidcUnverified::from_str(atr.id_token.as_ref().unwrap()).expect("Failed to parse id_token");

    let oidc = jws_validator
        .verify(&oidc_unverified)
        .expect("Failed to verify oidc")
        .verify_exp(0)
        .expect("Failed to check exp");

    // This is mostly checked inside of idm/oauth2.rs. This is more to check the oidc
    // token and the userinfo endpoints.
    assert_eq!(
        oidc.iss,
        rsclient.make_url("/oauth2/openid/test_integration")
    );
    eprintln!("{:?}", oidc.s_claims.email);
    assert_eq!(oidc.s_claims.email.as_deref(), Some(NOT_ADMIN_TEST_EMAIL));
    assert_eq!(oidc.s_claims.email_verified, Some(true));

    eprintln!("{:?}", oidc.claims);
    assert_eq!(
        oidc.claims.get("test_claim").and_then(|v| v.as_str()),
        Some("claim_a claim_b")
    );

    // Check the preflight works.
    let response = client
        .request(
            reqwest::Method::OPTIONS,
            rsclient.make_url("/oauth2/openid/test_integration/userinfo"),
        )
        .send()
        .await
        .expect("Failed to send userinfo preflight request.");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    let cors_header: &str = response
        .headers()
        .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN)
        .expect("missing access-control-allow-origin header")
        .to_str()
        .expect("invalid access-control-allow-origin header");
    assert!(cors_header.eq("*"));

    let response = client
        .get(rsclient.make_url("/oauth2/openid/test_integration/userinfo"))
        .bearer_auth(atr.access_token.clone())
        .send()
        .await
        .expect("Failed to send userinfo request.");

    let userinfo = response
        .json::<OidcToken>()
        .await
        .expect("Unable to decode OidcToken from userinfo");

    eprintln!("userinfo {userinfo:?}");
    eprintln!("oidc {oidc:?}");

    assert_eq!(userinfo, oidc);

    // auth back with admin so we can test deleting things
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());
    rsclient
        .idm_oauth2_rs_delete_sup_scope_map(TEST_INTEGRATION_RS_ID, TEST_INTEGRATION_RS_GROUP_ALL)
        .await
        .expect("Failed to update oauth2 scopes");
}

#[kanidmd_testkit::test]
async fn test_oauth2_token_post_bad_bodies(rsclient: KanidmClient) {
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .no_proxy()
        .build()
        .expect("Failed to create client.");

    // test for a bad-body request on token
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_ENDPOINT))
        .form(&serde_json::json!({}))
        // .bearer_auth(atr.access_token.clone())
        .send()
        .await
        .expect("Failed to send token request.");
    println!("{:?}", response);
    assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);

    // test for a bad-auth request
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_INTROSPECT_ENDPOINT))
        .form(&serde_json::json!({ "token": "lol" }))
        .send()
        .await
        .expect("Failed to send token introspection request.");
    println!("{:?}", response);
    assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[kanidmd_testkit::test]
async fn test_oauth2_token_revoke_post(rsclient: KanidmClient) {
    let res = rsclient
        .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD)
        .await;
    assert!(res.is_ok());

    let client = reqwest::Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .no_proxy()
        .build()
        .expect("Failed to create client.");

    // test for a bad-body request on token
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_REVOKE_ENDPOINT))
        .form(&serde_json::json!({}))
        .bearer_auth("lolol")
        .send()
        .await
        .expect("Failed to send token request.");
    println!("{:?}", response);
    assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);

    // test for a invalid format request on token
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_REVOKE_ENDPOINT))
        .json("")
        .bearer_auth("lolol")
        .send()
        .await
        .expect("Failed to send token request.");
    println!("{:?}", response);

    assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);

    // test for a bad-body request on token
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_REVOKE_ENDPOINT))
        .form(&serde_json::json!({}))
        .bearer_auth("Basic lolol")
        .send()
        .await
        .expect("Failed to send token request.");
    println!("{:?}", response);
    assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);

    // test for a bad-body request on token
    let response = client
        .post(rsclient.make_url(OAUTH2_TOKEN_REVOKE_ENDPOINT))
        .body(serde_json::json!({}).to_string())
        .bearer_auth("Basic lolol")
        .send()
        .await
        .expect("Failed to send token request.");
    println!("{:?}", response);
    assert_eq!(response.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
}
