diff --git a/src/defs/openid.rs b/src/defs/openid.rs new file mode 100644 index 0000000..bb55838 --- /dev/null +++ b/src/defs/openid.rs @@ -0,0 +1,188 @@ +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 +} \ No newline at end of file