use std::collections::HashMap; use serde::{Serialize, Deserialize}; // use std::io::{Result, ErrorKind}; use std::error::Error; //use std::str::FromStr; type BoxResult = Result>; use url::Url; use jsonwebtoken::{Algorithm, DecodingKey, Validation}; use openidconnect::{ core::{ CoreClient, CoreProviderMetadata, CoreResponseType, }, reqwest::async_http_client, HttpRequest, AuthenticationFlow, ClientId, ClientSecret, CsrfToken, IssuerUrl, Nonce, RedirectUrl, Scope, PkceCodeChallenge, PkceCodeVerifier, }; use axum::{http::method::Method, http::HeaderMap, http::header::CONTENT_TYPE}; use crate::defs::Config; fn default_empty() -> String { String::from("") } fn default_expire_in() -> u64 { 0 } fn default_algorithm() -> String { String::from("RS256") } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct OpenidClaims { pub name: String, pub owner: String, pub email: String, // exp: i64, } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct OpenidTokens { # [serde(default = "default_empty")] pub access_token: String, # [serde(default = "default_empty")] pub id_token: String, # [serde(default = "default_empty")] pub refresh_token: String, # [serde(default = "default_empty")] pub token_type: String, # [serde(default = "default_expire_in")] pub expire_in: u64, # [serde(default = "default_empty")] pub scope: String, } #[derive(Default,Deserialize,Serialize,Debug,Clone)] pub struct OpenidData { #[serde(default = "default_empty")] pub code: String, #[serde(default = "default_empty")] pub state: String, } #[derive(Debug, Clone, Serialize, Deserialize,Default)] pub struct OpenidConf { #[serde(default = "default_algorithm")] pub algorithm: String, pub access_token_url: String, pub disable_signature: bool, pub endpoint: String, pub client_id: String, pub client_secret: String, pub certificate: String, pub org_name: String, pub app_name: String, } pub struct OpenidCli { pub client: CoreClient, pub pk_challenge: PkceCodeChallenge, pub pk_verifier: PkceCodeVerifier, } pub struct OpenidClient { pub url: Url, pub token: CsrfToken, pub nonce: Nonce, } /* fn handle_error(fail: &T, msg: &'static str) { let mut err_msg = format!("ERROR: {}", msg); let mut cur_fail: Option<&dyn std::error::Error> = Some(fail); while let Some(cause) = cur_fail { err_msg += &format!("\n caused by: {}", cause); cur_fail = cause.source(); } println!("{}", err_msg); // exit(1); } */ impl OpenidConf { pub async fn client(&self, return_url: String) -> BoxResult { let app_client_id = ClientId::new(self.client_id.to_owned()); let app_client_secret = ClientSecret::new(self.client_secret.to_owned()); let issuer_url = IssuerUrl::new(self.endpoint.to_owned())?; let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client).await?; let redirect_url = RedirectUrl::new(return_url.to_owned())?; let (pk_challenge, pk_verifier) = PkceCodeChallenge::new_random_sha256(); // Set up the config for the App OAuth2 process. Ok( OpenidCli { client: CoreClient::from_provider_metadata( provider_metadata, app_client_id, Some(app_client_secret), ).set_redirect_uri(redirect_url), pk_challenge, pk_verifier, } ) } pub async fn get_auth(&self, openid_cli: &OpenidCli) -> BoxResult { // Generate the authorization URL to which we'll redirect the user. //let pk_challenge = openid_cli.pk_challenge.clone(); let (url, token, nonce) = openid_cli.client .authorize_url( AuthenticationFlow::::AuthorizationCode, CsrfToken::new_random, Nonce::new_random, ) // This example is requesting access to the the user's profile including email. .add_scope(Scope::new("name".to_string())) .add_scope(Scope::new("email".to_string())) // Set the PKCE code challenge. // .set_pkce_challenge(pk_challenge) .url(); Ok(OpenidClient { url, token, nonce }) } pub async fn get_token(&self, _openid_cli: &OpenidCli, auth_code: String, _str_nonce: String) -> BoxResult { // let nonce = Nonce::new(str_nonce); // this is because PkceCodeVerifier des not have clone derive // let str_pk_verifier = serde_json::to_string(&openid_cli.pk_verifier).unwrap_or_default(); // let pk_verifier: PkceCodeVerifier = serde_json::from_str(&str_pk_verifier)?; let mut header_map = HeaderMap::new(); header_map.insert(CONTENT_TYPE, "application/json".parse()?); let mut body_data: HashMap = HashMap::new(); body_data.insert(String::from("grant_type"), String::from("authorization_code")); body_data.insert(String::from("client_id"), self.client_id.to_owned()); body_data.insert(String::from("client_secret"), self.client_secret.to_owned()); body_data.insert(String::from("code"), auth_code.to_owned()); let str_body = serde_json::to_string(&body_data).unwrap_or_default(); let body = str_body.as_bytes().to_vec(); if self.access_token_url.is_empty() { return Err(Box::try_from(format!("Invalid openid access token url"))?); } let openid_request = HttpRequest{ url: Url::parse(&self.access_token_url)?, method: Method::POST, headers: header_map, body }; let openid_response = async_http_client(openid_request).await?; // dbg!(&openid_response.headers); let vec_to_string = String::from_utf8(openid_response.body)?; let openid_tokens: OpenidTokens = serde_json::from_str(&vec_to_string).unwrap_or(OpenidTokens::default()); let algorithm = match self.algorithm.as_str() { "RS256" => Algorithm::RS256, _ => Algorithm::RS256, }; let mut validation = Validation::new(algorithm); if self.disable_signature { validation.insecure_disable_signature_validation(); } let res = jsonwebtoken::decode::( &openid_tokens.id_token, &DecodingKey::from_secret(&[]), &validation )?; Ok(res.claims) } } pub async fn collect_openid_clients(config: Config, return_url: &str) -> HashMap { let mut clients = HashMap::new(); for (key, openid_conf) in config.openid_auths.clone().into_iter() { match openid_conf.client(return_url.to_owned()).await { Ok(cli) => { clients.insert(key.to_owned(),cli); println!("OpenID {} added", key) }, Err(e) => println!("Openid {} error: {} ",key, e), } } clients }