From cf66a962be930090a31a971c8a7814740a909919 Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:57:36 +0100 Subject: [PATCH 1/2] Fixed oidc build. --- src/auth_middleware.rs | 62 +++++--------------------- src/constants/inner_constants.rs | 4 +- src/main.rs | 76 +++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 53 deletions(-) diff --git a/src/auth_middleware.rs b/src/auth_middleware.rs index 30ca4f17..c204681c 100644 --- a/src/auth_middleware.rs +++ b/src/auth_middleware.rs @@ -1,4 +1,6 @@ +use std::collections::HashSet; use std::future::Future; +use std::ops::Deref; use std::pin::Pin; use std::rc::Rc; use std::sync::Mutex; @@ -13,16 +15,14 @@ use base64::engine::general_purpose; use futures_util::future::{LocalBoxFuture, Ready}; use dotenv::var; use jsonwebtoken::{Algorithm, decode, DecodingKey, Validation}; -use jsonwebtoken::jwk::Jwk; +use jsonwebtoken::jwk::{AlgorithmParameters, CommonParameters, Jwk, KeyAlgorithm, RSAKeyParameters, RSAKeyType}; use log::info; use serde_json::{from_str, Value}; use crate::constants::inner_constants::{BASIC_AUTH, OIDC_AUTH, PASSWORD, USERNAME}; use crate::{DbPool}; use crate::models::user::User; use sha256::digest; -use crate::models::oidc_model::{CustomJwk, CustomJwkSet}; -use crate::mutex::LockResultExt; -use crate::service::jwkservice::JWKService; +use crate::models::oidc_model::{CustomJwk}; use crate::utils::environment_variables::is_env_var_present_and_true; pub struct AuthFilter { @@ -154,50 +154,17 @@ impl AuthFilterMiddleware where B: 'static + MessageBody, S: 'static + } let token = token_res.unwrap().replace("Bearer ", ""); - let start = SystemTime::now(); - let since_the_epoch = start - .duration_since(UNIX_EPOCH) - .expect("Time went backwards").as_secs(); + let jwk = req.app_data::>>().cloned().unwrap(); - let response:CustomJwkSet; - let binding = req.app_data::>>().cloned().unwrap(); - let mut jwk_service = binding.lock() - .ignore_poison(); - match jwk_service.jwk.clone() { - Some(jwk)=>{ - if since_the_epoch-jwk_service.timestamp>3600{ - //refetch and update timestamp - info!("Renewing jwk set"); - response = AuthFilter::get_jwk(); - jwk_service.jwk = Some(response.clone()); - jwk_service.timestamp = since_the_epoch - } - else{ - info!("Using cached jwk set"); - response = jwk; - } - } - None=>{ - // Fetch on cold start - response = AuthFilter::get_jwk(); - jwk_service.jwk = Some(response.clone()); - jwk_service.timestamp = since_the_epoch - } - } + // Create a DecodingKey from a PEM-encoded RSA string - // Filter out all unknown algorithms - let response = response.clone().keys.into_iter().filter(|x| { - x.alg.eq(&"RS256") - }).collect::>(); - let jwk = response.clone(); - let custom_jwk = jwk.get(0).expect("Your jwk set needs to have RS256"); + let key = DecodingKey::from_jwk(&jwk.as_ref().clone().unwrap()).unwrap(); + let mut validation = Validation::new(Algorithm::RS256); + validation.aud = Some(req.app_data::>>().unwrap().clone().into_inner() + .deref().clone()); - let jwk_string = serde_json::to_string(&custom_jwk).unwrap(); - let jwk = from_str::(&jwk_string).unwrap(); - let key = DecodingKey::from_jwk(&jwk).unwrap(); - let validation = Validation::new(Algorithm::RS256); return match decode::(&token, &key, &validation) { Ok(decoded) => { let username = decoded.claims.get("preferred_username").unwrap().as_str().unwrap(); @@ -237,7 +204,8 @@ impl AuthFilterMiddleware where B: 'static + MessageBody, S: 'static + } } }, - _ => { + Err(e) => { + info!("Error decoding token: {:?}", e); Box::pin(ok(req.error_response(ErrorForbidden("Forbidden")) .map_into_right_body())) } @@ -271,12 +239,6 @@ impl AuthFilter{ (username.to_string(), password.to_string()) } - pub fn get_jwk() -> CustomJwkSet { - let jwk_uri = var("OIDC_JWKS").expect("OIDC_JWKS must be set"); - reqwest::blocking::get(jwk_uri).unwrap() - .json::().unwrap() - } - pub fn basic_auth_login(rq: String) -> (String, String) { let (u,p) = Self::extract_basic_auth(rq.as_str()); diff --git a/src/constants/inner_constants.rs b/src/constants/inner_constants.rs index 36d55b7c..94f533fc 100644 --- a/src/constants/inner_constants.rs +++ b/src/constants/inner_constants.rs @@ -135,4 +135,6 @@ pub const MAX_FILE_TREE_DEPTH:i32 = 4; pub const COMMON_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \ -(KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"; \ No newline at end of file +(KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"; + +pub const OIDC_JWKS: &str = "OIDC_JWKS"; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 581b097c..e36e54d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,14 +15,17 @@ use clokwerk::{Scheduler, TimeUnits}; use std::sync::{Mutex}; use std::time::Duration; use std::{env, thread}; -use std::env::{args, var}; +use std::collections::HashSet; +use std::env::{args, var, VarError}; use std::io::Read; +use std::ops::Deref; use std::process::exit; use actix_web::body::{BoxBody, EitherBody}; use log::{info}; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; use diesel::r2d2::{ConnectionManager}; +use jsonwebtoken::jwk::{AlgorithmParameters, CommonParameters, Jwk, KeyAlgorithm, RSAKeyParameters, RSAKeyType}; use r2d2::{Pool}; use regex::Regex; use tokio::task::spawn_blocking; @@ -31,7 +34,7 @@ mod controllers; #[cfg(sqlite)] use crate::config::dbconfig::{ConnectionOptions}; use crate::config::dbconfig::{establish_connection, get_database_url}; -use crate::constants::inner_constants::{BASIC_AUTH, CSS, JS, OIDC_AUTH, TELEGRAM_API_ENABLED, TELEGRAM_BOT_CHAT_ID, TELEGRAM_BOT_TOKEN}; +use crate::constants::inner_constants::{BASIC_AUTH, CSS, JS, OIDC_AUTH, OIDC_CLIENT_ID, OIDC_JWKS, TELEGRAM_API_ENABLED, TELEGRAM_BOT_CHAT_ID, TELEGRAM_BOT_TOKEN}; use crate::controllers::api_doc::ApiDoc; use crate::controllers::notification_controller::{ dismiss_notifications, get_unread_notifications, @@ -60,6 +63,7 @@ mod models; mod service; use crate::gpodder::parametrization::get_client_parametrization; use crate::gpodder::routes::get_gpodder_api; +use crate::models::oidc_model::{CustomJwk, CustomJwkSet}; use crate::models::podcasts::Podcast; use crate::models::session::Session; use crate::models::settings::Setting; @@ -222,8 +226,74 @@ async fn main() -> std::io::Result<()> { thread::sleep(Duration::from_millis(1000)); } }); + + let key_param: Option; + let mut hash = HashSet::new(); + let jwk: Option; + + match var(OIDC_JWKS) { + Ok(jwk_uri)=>{ + let resp = reqwest::get(&jwk_uri).await.unwrap() + .json::().await; + + match resp { + Ok(res) => { + let oidc = res + .clone() + .keys + .into_iter() + .filter(|x| x.alg.eq(&"RS256")) + .collect::>() + .first() + .map(|x| x.clone()); + + if oidc.is_none() { + panic!("No RS256 key found in JWKS") + } + + key_param = Some(RSAKeyParameters { + e: oidc.clone().unwrap().e, + n: oidc.unwrap().n.clone(), + key_type: RSAKeyType::RSA, + }); + + jwk = Some(Jwk{ + common: CommonParameters{ + public_key_use: None, + key_id: None, + x509_url: None, + x509_chain: None, + x509_sha1_fingerprint: None, + key_operations: None, + key_algorithm: Some(KeyAlgorithm::RS256), + x509_sha256_fingerprint: None, + }, + algorithm: AlgorithmParameters::RSA(key_param.clone().unwrap()), + }); + }, + Err(_) => { + panic!("Error downloading OIDC") + } + } + } + _ => { + key_param = None; + jwk = None; + } + } + + match var(OIDC_CLIENT_ID){ + Ok(client_id)=>{ + hash.insert(client_id); + } + Err(_)=>{} + } + HttpServer::new(move || { App::new() + .app_data(Data::new(key_param.clone())) + .app_data(Data::new(jwk.clone())) + .app_data(Data::new(hash.clone())) .service(redirect("/", var("SUB_DIRECTORY").unwrap()+"/ui/")) .service(get_gpodder_api(environment_service.clone())) .service(get_global_scope()) @@ -284,6 +354,8 @@ pub fn get_global_scope() -> Scope { fn get_private_api() -> Scope>, Error = actix_web::Error, InitError = ()>> { let middleware = AuthFilter::new(); + + web::scope("") .wrap(middleware) .service(delete_playlist_item) From 00cde3f477711e30a141ea3fa6d8ee7da8dcbc96 Mon Sep 17 00:00:00 2001 From: SamTV12345 <40429738+samtv12345@users.noreply.github.com> Date: Mon, 6 Nov 2023 15:00:06 +0100 Subject: [PATCH 2/2] Fixed clippy. --- src/auth_middleware.rs | 10 +++++----- src/main.rs | 15 ++++++--------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/auth_middleware.rs b/src/auth_middleware.rs index c204681c..c3cd9b47 100644 --- a/src/auth_middleware.rs +++ b/src/auth_middleware.rs @@ -3,8 +3,8 @@ use std::future::Future; use std::ops::Deref; use std::pin::Pin; use std::rc::Rc; -use std::sync::Mutex; -use std::time::{SystemTime, UNIX_EPOCH}; + + use actix::fut::{ok}; use futures_util::FutureExt; use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, HttpMessage, web}; @@ -15,14 +15,14 @@ use base64::engine::general_purpose; use futures_util::future::{LocalBoxFuture, Ready}; use dotenv::var; use jsonwebtoken::{Algorithm, decode, DecodingKey, Validation}; -use jsonwebtoken::jwk::{AlgorithmParameters, CommonParameters, Jwk, KeyAlgorithm, RSAKeyParameters, RSAKeyType}; +use jsonwebtoken::jwk::{Jwk}; use log::info; -use serde_json::{from_str, Value}; +use serde_json::{Value}; use crate::constants::inner_constants::{BASIC_AUTH, OIDC_AUTH, PASSWORD, USERNAME}; use crate::{DbPool}; use crate::models::user::User; use sha256::digest; -use crate::models::oidc_model::{CustomJwk}; + use crate::utils::environment_variables::is_env_var_present_and_true; pub struct AuthFilter { diff --git a/src/main.rs b/src/main.rs index e36e54d9..20f1a5e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,9 +16,9 @@ use std::sync::{Mutex}; use std::time::Duration; use std::{env, thread}; use std::collections::HashSet; -use std::env::{args, var, VarError}; +use std::env::{args, var}; use std::io::Read; -use std::ops::Deref; + use std::process::exit; use actix_web::body::{BoxBody, EitherBody}; use log::{info}; @@ -244,8 +244,7 @@ async fn main() -> std::io::Result<()> { .into_iter() .filter(|x| x.alg.eq(&"RS256")) .collect::>() - .first() - .map(|x| x.clone()); + .first().cloned(); if oidc.is_none() { panic!("No RS256 key found in JWKS") @@ -282,13 +281,11 @@ async fn main() -> std::io::Result<()> { } } - match var(OIDC_CLIENT_ID){ - Ok(client_id)=>{ - hash.insert(client_id); - } - Err(_)=>{} + if let Ok(client_id) = var(OIDC_CLIENT_ID) { + hash.insert(client_id); } + HttpServer::new(move || { App::new() .app_data(Data::new(key_param.clone()))