From 97a411bdc80332a801b852757d9de140da573e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20P=C3=A9rez?= Date: Sun, 17 Sep 2023 01:34:23 +0100 Subject: [PATCH] chore: add and fix hanldlers to use openid with casdoor for Single Sign-On --- src/handlers/users_handlers.rs | 274 ++++++++++++++++++++++++++------- 1 file changed, 220 insertions(+), 54 deletions(-) diff --git a/src/handlers/users_handlers.rs b/src/handlers/users_handlers.rs index 7f7b6d6..8f8d204 100644 --- a/src/handlers/users_handlers.rs +++ b/src/handlers/users_handlers.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::Arc; use urlencoding::{encode,decode}; // use tokio::sync::RwLock; @@ -33,6 +34,9 @@ use crate::{ TotpMode, Random, AppConnectInfo, + OpenidData, + OpenidCli, +// OpenidClaims, // UserNotifyData, // TOKEN_AUTH_VALUE, // TOKEN_KEY_VALUE, @@ -73,7 +77,7 @@ pub fn users_router_handlers() -> Router { // } let auth_state = get_auth_state(true, &cookies, &app_dbs).await; // has_cookie=true; - // TODO check value + // TODO check value // let sid: String = format!("{}",&auth_state.sid()); // println!("auth_sid: {}",&sid); // println!("id: {}",&auth_state.id()); @@ -205,7 +209,7 @@ pub fn users_router_handlers() -> Router { "signup_handler" ); if !auth_state.is_admin() { - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ println!("Count error: {}",e); 0 }); @@ -215,7 +219,7 @@ pub fn users_router_handlers() -> Router { let _ = req_handler.trace_req(msg); return Redirect::temporary( &format!("/")).into_response(); } - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ println!("Count error: {}",e); -1 }); @@ -237,7 +241,7 @@ pub fn users_router_handlers() -> Router { Ok(totp) => { req_handler.context.insert("otp_code", &totp.get_secret_base32()); req_handler.context.insert("otp_url", &totp.get_url()); - req_handler.context.insert("otp_qr", &totp.get_qr().unwrap_or_default()); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); req_handler.context.insert("totp_digits", &app_dbs.config.totp_digits); req_handler.context.insert("totp_algorithm",&format!("{}",&app_dbs.config.totp_algorithm)); }, @@ -347,7 +351,7 @@ pub fn users_router_handlers() -> Router { "Error" ).into_response(); } - } + } } else { otp_enabled = false; } @@ -370,14 +374,14 @@ pub fn users_router_handlers() -> Router { } else { DEFAULT_ROLES.to_owned() }; - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ let _ = req_handler.trace_req( format!("Users count error: {}",e)); println!("Count error: {}",e); -1 }); let isadmin = if user_data.id == "A" || total_users < 1 { true - } else { + } else { false }; let mut new_items = user_data.items.to_owned(); @@ -428,7 +432,6 @@ pub fn users_router_handlers() -> Router { } }; let session_token = req_handler.new_token(); - println!("session: {}", &session_token.to_string()); let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie); } @@ -455,7 +458,7 @@ pub fn users_router_handlers() -> Router { &random, "login_handler" ); - let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ let _ = req_handler.trace_req( format!("Users count error: {}",e)); println!("Count error: {}",e); -1 @@ -469,6 +472,7 @@ pub fn users_router_handlers() -> Router { if req_handler.req_header.is_browser() { res_headers.append(axum::http::header::CONTENT_TYPE,"text/html; charset=utf-8".parse().unwrap()); } + req_handler.context.insert("openid_auths", &app_dbs.config.openid_auths.clone().into_keys().collect::>()); // req_handler.context.insert("with_menu", "1"); req_handler.context.insert("use_mail", &app_dbs.config.use_mail); req_handler.context.insert("totp_mode", &format!("{}",&app_dbs.config.totp_mode)); @@ -487,14 +491,90 @@ pub fn users_router_handlers() -> Router { result.to_owned() ).into_response() } - async fn post_login_handler( + async fn login_openid_handler( header: HeaderMap, uri: Uri, Extension(app_dbs): Extension>, Extension(cookies): Extension, Extension(random): Extension, + Extension(openid_clients): Extension>>, ConnectInfo(app_connect_info): ConnectInfo, - Json(user_login): Json, + axum::extract::Path(data): axum::extract::Path, + ) -> Response { + dbg!(&header); + dbg!(&uri); + dbg!(&data); + SessionStoreDB::cleanup_data(&app_dbs).await; + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let req_handler = ReqHandler::new( + ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + &app_dbs, + &uri, + &auth_state, + &random, + "login_handler" + ); + let total_users = User::count(&app_dbs.user_store).await.unwrap_or_else(|e|{ + let _ = req_handler.trace_req( format!("Users count error: {}",e)); + println!("Count error: {}",e); + -1 + }); + if total_users < 1 { + let _ = req_handler.trace_req(String::from("No users found")); + return Redirect::temporary( &format!("/signup")).into_response(); + } + if data.is_empty() { + return Redirect::temporary( &format!("/login")).into_response(); + } + let (str_user_data,url) = if let Some(openid_conf) = app_dbs.config.openid_auths.get(&data) { + if let Some(openid_cli) = openid_clients.get(&data) { + dbg!(&openid_conf); + match openid_conf.get_auth(openid_cli).await { + Ok(openid) => { + //let _ = req_handler.trace_req(format!("User '{}' created",&user_data.name)); + let str_user_data = format!("{}|{}|{}", &data, openid.token.secret(), openid.nonce.secret()); + //let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + //if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + //let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + /*req_handler = ReqHandler::new( + req_handler.req_header, + &app_dbs, + &uri, + &new_auth_state, + &random, + "post_login_handler" + );*/ + //let _ = req_handler.trace_req(format!("user '{}', new token: '{}', cookie: '{}' ",&user_login.name, &session_token, &session_cookie)); + (str_user_data, format!("{}", openid.url.to_string())) + }, + Err(e) => { + println!("Error openid login handler: {}", e); + (String::from(""), String::from("/login")) + } + } + } else { + (String::from(""), String::from("/login")) + } + } else { + (String::from(""), String::from("/login")) + }; + let session_token = req_handler.new_token(); + dbg!(&url); + let session_cookie = add_session_cookie(true,&cookies, &session_token, &str_user_data, 0, &app_dbs, "/").await; + if app_dbs.config.verbose > 1 { println!("session cookie: {}", &session_cookie) }; + let _new_auth_state = AuthState::from_cookie(session_cookie.to_string(), &app_dbs).await; + return Redirect::temporary(&format!("{}",url)).into_response() + } + + async fn login_user ( + header: HeaderMap, + uri: Uri, + app_dbs: Arc, + cookies: Cookies, + random: Random, + app_connect_info: AppConnectInfo, + user_login: UserLogin, + source: &str, ) -> Response { let auth_state = get_auth_state(true, &cookies, &app_dbs).await; let mut req_handler = ReqHandler::new( @@ -505,7 +585,8 @@ pub fn users_router_handlers() -> Router { &random, "post_login_handler" ); - if user_login.name.is_empty() || user_login.password.is_empty() { + dbg!(&user_login); + if user_login.name.is_empty() || source != "from_login_openid" && user_login.password.is_empty() { let _ = req_handler.trace_req(String::from("Empty name or password")); // return Err(error_page(&SignupError::MissingDetails)); return ( @@ -514,6 +595,8 @@ pub fn users_router_handlers() -> Router { "Error" ).into_response(); } + dbg!(&user_login); + dbg!(&source); let field = if user_login.name.contains("@") { "email" } else { @@ -523,6 +606,7 @@ pub fn users_router_handlers() -> Router { println!("Error select: {}", e); User::default() }); + dbg!(&user_sel); if user_sel.name.is_empty() { let _ = req_handler.trace_req(format!("No name '{}' found",&user_login.name)); // User not exists @@ -532,6 +616,7 @@ pub fn users_router_handlers() -> Router { "Error data" ).into_response(); } + dbg!(&user_sel); if user_sel.status != UserStatus::Active && user_sel.status != UserStatus::Created { let _ = req_handler.trace_req(format!("user '{}' in not valid status: {}",&user_login.name, &user_sel.status)); return ( @@ -540,53 +625,59 @@ pub fn users_router_handlers() -> Router { "Error status" ).into_response(); } - if verify_password(&user_login.password, &user_sel.password).is_err() { - let _ = req_handler.trace_req(format!("user '{}' not valid password: {}",&user_login.name, &user_sel.password)); - println!("password NOT valid"); - // TODO - //return Err(error_page(&SignupError::PasswordsDoNotMatch)) - return ( - StatusCode::BAD_REQUEST, - req_handler.req_header.header, - "Error data" - ).into_response(); - } - let result=format!("{}:{}","OK",&user_sel.otp_verified); - if app_dbs.config.totp_mode != TotpMode::No { - if user_login.otp_auth.is_empty() - && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) - { - let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}",&user_login.name, &user_sel.otp_enabled)); + dbg!(&user_sel); + let result = if source == "from_login_openid" { + format!("{}:true", "OK") + } else { + if verify_password(&user_login.password, &user_sel.password).is_err() { + let _ = req_handler.trace_req(format!("user '{}' not valid password: {}", &user_login.name, &user_sel.password)); + println!("password NOT valid"); + // TODO + //return Err(error_page(&SignupError::PasswordsDoNotMatch)) return ( + StatusCode::BAD_REQUEST, req_handler.req_header.header, - result + "Error data" ).into_response(); - } else if user_sel.otp_enabled && user_sel.otp_verified - && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() - { - match req_handler.otp_check(&user_sel.otp_base32,&user_login.otp_auth, &user_sel.otp_defs) { - Ok(val) => { - if !val { - let _ = req_handler.trace_req(format!("user '{}' not valid TOTP code",&user_login.name)); + } + let result = format!("{}:{}", "OK", &user_sel.otp_verified); + if app_dbs.config.totp_mode != TotpMode::No { + if user_login.otp_auth.is_empty() + && (app_dbs.config.totp_mode == TotpMode::Mandatory || user_sel.otp_enabled) + { + let _ = req_handler.trace_req(format!("user '{}' not valid Totp: {}", &user_login.name, &user_sel.otp_enabled)); + return ( + req_handler.req_header.header, + result + ).into_response(); + } else if user_sel.otp_enabled && user_sel.otp_verified + && !user_sel.otp_base32.is_empty() && !user_sel.otp_defs.is_empty() + { + match req_handler.otp_check(&user_sel.otp_base32, &user_login.otp_auth, &user_sel.otp_defs) { + Ok(val) => { + if !val { + let _ = req_handler.trace_req(format!("user '{}' not valid TOTP code", &user_login.name)); + return ( + StatusCode::UNAUTHORIZED, + req_handler.req_header.header, + "Error" + ).into_response(); + } + }, + Err(e) => { + let _ = req_handler.trace_req(format!("user '{}' TOTP check error: {}", &user_login.name, e)); + println!("TOTP check: {}", e); return ( StatusCode::UNAUTHORIZED, req_handler.req_header.header, "Error" ).into_response(); } - }, - Err(e) => { - let _ = req_handler.trace_req(format!("user '{}' TOTP check error: {}",&user_login.name,e)); - println!("TOTP check: {}", e); - return ( - StatusCode::UNAUTHORIZED, - req_handler.req_header.header, - "Error" - ).into_response(); } - } - } - } + } + } + result + }; user_sel.lastaccess = chrono::Utc::now().timestamp().to_string(); if user_sel.status != UserStatus::Active { user_sel.status = UserStatus::Active } let user_data = user_sel.session_data(); @@ -610,11 +701,11 @@ pub fn users_router_handlers() -> Router { if app_dbs.config.protocol == "https" { ( req_handler.req_header.header, - result + result ).into_response() } else { ( - result + result ).into_response() } }, @@ -628,7 +719,78 @@ pub fn users_router_handlers() -> Router { } } } - + async fn post_login_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Json(user_login): Json, + ) -> Response { + login_user ( header, uri, + app_dbs, cookies, random, + app_connect_info, + user_login, + "post_login" + ).await + } + async fn from_login_openid_handler( + header: HeaderMap, + uri: Uri, + Extension(app_dbs): Extension>, + Extension(cookies): Extension, + Extension(random): Extension, + ConnectInfo(app_connect_info): ConnectInfo, + Extension(openid_clients): Extension>>, + //axum::extract::Path(data): axum::extract::Path, + data: axum::extract::Query, + // Json(user_login): Json, + ) -> Response { + let auth_state = get_auth_state(true, &cookies, &app_dbs).await; + let user_data = auth_state.user_data(); + //let _req_handler = ReqHandler::new( + // ReqHeaderMap::new(header, &format!("{}",&uri.path().to_string()), &app_connect_info), + // &app_dbs, + // &uri, + // &auth_state, + // &random, + // "post_login_handler" + //); + if user_data.len() > 1 && user_data[1] == data.state { + println!("OK State {} == {}", user_data[1], data.state); + } + let mut user_login = UserLogin::default(); + if let Some(openid_conf) = app_dbs.config.openid_auths.get(&user_data[0]) { + if let Some(openid_cli) = openid_clients.get(&user_data[0]) { + dbg!(openid_conf); + match openid_conf.get_token(openid_cli, data.code.to_owned(), user_data[2].to_owned()).await { + Ok(data) => { + dbg!("{}", &data); + user_login = UserLogin { + name: data.name.to_owned(), + password: String::from(""), + otp_auth: String::from(""), + email: data.email.to_owned(), + }; + }, + Err(e) => { + println!("Error openid login handler: {}", e); + } + } + } + } + if ! user_login.name.is_empty() { + let _res = login_user(header, uri, + app_dbs, cookies, random, + app_connect_info, + user_login, + "from_login_openid" + ).await; + } + let url="https://tiicl.tls13.io:8800"; + Redirect::temporary(&format!("{}",url)).into_response() + } async fn logout_handler( header: HeaderMap, uri: Uri, @@ -856,7 +1018,7 @@ pub fn users_router_handlers() -> Router { Ok(totp) => { req_handler.context.insert("otp_code", &user_sel.otp_base32); req_handler.context.insert("otp_url", &user_sel.otp_auth_url); - req_handler.context.insert("otp_qr", &totp.get_qr().unwrap_or_default()); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); }, Err(e) => { println!("User settings error totp: {}",e); @@ -867,7 +1029,7 @@ pub fn users_router_handlers() -> Router { Ok(totp) => { req_handler.context.insert("otp_code", &totp.get_secret_base32()); req_handler.context.insert("otp_url", &totp.get_url()); - req_handler.context.insert("otp_qr", &totp.get_qr().unwrap_or_default()); + req_handler.context.insert("otp_qr", &totp.get_qr_base64().unwrap_or_default()); }, Err(e) => { println!("Error TOTP generartor: {}",e); @@ -1707,6 +1869,10 @@ pub fn users_router_handlers() -> Router { .route("/login", get(login_handler)) .route("/login", post(post_login_handler)) + + .route("/openid/:data", get(login_openid_handler)) + //.route("/from_openid/:data", get(from_login_openid_handler)) + .route("/from_openid", get(from_login_openid_handler)) .route("/signup", get(signup_handler)) .route("/signup", post(post_signup_handler))