chore: add openid with casdoor for Single Sign-On
This commit is contained in:
parent
9a0a65aa91
commit
1b0dafca9a
188
src/defs/openid.rs
Normal file
188
src/defs/openid.rs
Normal file
@ -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<T> = Result<T,Box<dyn Error>>;
|
||||||
|
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<T: std::error::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<OpenidCli> {
|
||||||
|
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<OpenidClient> {
|
||||||
|
// 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::<CoreResponseType>::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<OpenidClaims> {
|
||||||
|
// 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<String, String> = 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::<OpenidClaims>(
|
||||||
|
&openid_tokens.id_token,
|
||||||
|
&DecodingKey::from_secret(&[]),
|
||||||
|
&validation
|
||||||
|
)?;
|
||||||
|
Ok(res.claims)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn collect_openid_clients(config: Config, return_url: &str) -> HashMap<String,OpenidCli> {
|
||||||
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user