diff --git a/src/handlers.rs b/src/handlers.rs index 1319bad..5b361e6 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -6,4 +6,5 @@ pub mod jwt; pub mod sessions; pub mod router; -pub mod kratos; \ No newline at end of file +pub mod kratos; +pub mod extauthz; \ No newline at end of file diff --git a/src/handlers/extauthz.rs b/src/handlers/extauthz.rs new file mode 100644 index 0000000..8b1ab51 --- /dev/null +++ b/src/handlers/extauthz.rs @@ -0,0 +1,132 @@ +// +/*! libresignin +*/ +// Copyright 2022, Jesús Pérez Lorenzo +// +use axum::{ +// response::Html, +// routing::get, +// routing::post, +// Router, + // extract::{self,Extension,Path,Query}, +// extract::{Extension,Path}, + http::{ + header::{HeaderMap,HeaderName,HeaderValue}, + // Request, StatusCode, + }, + // body::{Bytes, Body}, + response::{IntoResponse, Headers}, +}; + +// use crate::defs::{DataDBs}; +// use crate::utils::reqenv::ReqEnv; + +const CHECK_HEADER: &'static str = "x-ext-authz"; +const ALLOWED_VALUE: &'static str = "allow"; +const RESULT_HEADER: &'static str = "x-ext-authz-check-result"; +const RECEIVED_HEADER: &'static str = "x-ext-authz-check-received"; +const OVERRIDE_HEADER: &'static str = "x-ext-authz-additional-header-override"; +// const OVERRIDE_GRPC_VALUE: &'static str = "grpc-additional-header-override-value"; +const RESULT_ALLOWED: &'static str = "allowed"; +const RESULT_DENIED: &'static str = "denied"; + +// pub fn create_headers(is_allowed: bool, str_head: &str, id: &str,token: &str,csrf_token: &str, cookie: &str) -> HeaderMap { +// let mut headers = HeaderMap::new(); +// if is_allowed { +// headers.insert(RESULT_HEADER, +// HeaderValue::from_str(RESULT_ALLOWED).unwrap_or(HeaderValue::from_static("")) +// ); +// } else { +// headers.insert(RESULT_HEADER, +// HeaderValue::from_str(RESULT_DENIED).unwrap_or(HeaderValue::from_static("")) +// ); +// } +// headers.insert(OVERRIDE_HEADER, +// HeaderValue::from_str(OVERRIDE_HEADER).unwrap_or(HeaderValue::from_static("")) +// ); +// headers.insert(RECEIVED_HEADER, +// HeaderValue::from_str(str_head).unwrap_or(HeaderValue::from_static("")) +// ); + // headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + // headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + // if cookie != "" { + // headers.insert(COOKIE, HeaderValue::from_str(cookie) + // .unwrap_or(HeaderValue::from_static("")) + // ); + // // headers.insert(SET_COOKIE, HeaderValue::from_str(cookie) + // // .unwrap_or(HeaderValue::from_static("")) + // // ); + // } + // if token != "" { + // headers.insert(AUTHORIZATION, HeaderValue::from_str(&format!("Bearer {}",token)) + // .unwrap_or(HeaderValue::from_static("")) + // ); + // } + // if csrf_token != "" { + // headers.insert("X-CSRF-Token", + // HeaderValue::from_str(csrf_token).unwrap_or(HeaderValue::from_static("")) + // ); + // } + // if id != "" { + // headers.insert("x-kratos-authenticated-identity-id", + // HeaderValue::from_str(id).unwrap_or(HeaderValue::from_static("")) + // ); + // } + // dbg!("{:#?}",&headers); + // dbg!("{:#?}",&headers); +// headers +// } +// pub async fn authz_handler(header: HeaderMap, req: Request<()> ) -> impl IntoResponse { +pub async fn authz_handler(header: HeaderMap) -> impl IntoResponse { + dbg!("{:#?}",&header); + let result: &str; + if let Some(check) = &header.get(CHECK_HEADER) { + match check.to_str() { + Ok(check_val) => + if ALLOWED_VALUE == check_val { + result = RESULT_ALLOWED; + } else { + result = RESULT_DENIED; + }, + Err(_) => result = RESULT_DENIED, + } + } else { + result = RESULT_DENIED; + } + //let headers = create_headers("",token,csrf_token,cookie); + // body, err := io.ReadAll(request.Body) + // if err != nil { + // log.Printf("[HTTP] read body failed: %v", err) + // } + // format!("%s %s%s, headers: %v, body: [%s]\n", request.Method, request.Host, request.URL, request.Header, body); + let str_head = format!("head"); + // let headers = create_headers(is_allowed,&str_head,"","","",""); + let res_headers: Vec<(HeaderName, HeaderValue)> = vec![ + (HeaderName::from_static(RESULT_HEADER), HeaderValue::from_static(result)), + (HeaderName::from_static(OVERRIDE_HEADER), HeaderValue::from_static(OVERRIDE_HEADER)), + (HeaderName::from_static(RECEIVED_HEADER), HeaderValue::from_str(&str_head).unwrap_or(HeaderValue::from_static(""))), + ]; + // if is_allowed { + // response.WriteHeader(StatusCode::OK) + // } else { + // response.WriteHeader(http.StatusForbidden) + // response.Write([]byte(denyBody)) + // } + dbg!("{:#?}",&res_headers); + Headers(res_headers) + // let html = format!( + // r#" + //
Done: {}
+ // "#,RESULT_ALLOWED); + // Html(html) +} +// pub async fn alive_handler() -> Html<&'static str> { +// Html("ok") +// } +// pub async fn ready_handler() -> Html<&'static str> { +// Html("ok") +// } +// pub fn router_handlers(web_router: Router) -> Router { +// web_router +// .route("/authz", get(authz_handler)) +// } diff --git a/src/handlers/kratos.rs b/src/handlers/kratos.rs index 2e02277..1616af7 100644 --- a/src/handlers/kratos.rs +++ b/src/handlers/kratos.rs @@ -17,7 +17,10 @@ use axum::{ response::IntoResponse, extract::{Extension,Path,Query}, //http::{Request, header::HeaderMap, Method,StatusCode}, - http::{header::HeaderMap,StatusCode}, + http::{ + header::{HeaderMap,HeaderName,HeaderValue}, + StatusCode, + }, // body::{Bytes, Body}, }; // use serde::{Deserialize, Serialize}; @@ -115,7 +118,7 @@ pub async fn registration_handler(header: HeaderMap, Extension(dbs): ExtensionRegister login
Status: {}
- "#,&reqenv.websrvr_url(),res.status); + "#,&reqenv.public_url(),res.status); }, Err(e) => { eprintln!("{}",e); @@ -162,6 +165,8 @@ pub async fn login_handler(header: HeaderMap, Extension(dbs): Extension let flow = String::from("self-service/login"); let query = String::from("/api?refresh=false&aal=&return_to="); let root_url = get_root_url(reqenv.websrvr().signin.protocol,reqenv.websrvr().signin.root,reqenv.websrvr().signin.port); + let mut headers = HeaderMap::new(); + let ret_status: StatusCode; let mut user_data = HashMap::new(); user_data.insert("password".to_string(),"19Ting22".to_string()); user_data.insert("password_identifier".to_string(),"jesuspl".to_string()); @@ -183,6 +188,11 @@ pub async fn login_handler(header: HeaderMap, Extension(dbs): Extension // res.session_resp.session_token); // return html.to_owned(); if res.status == StatusCode::OK { + headers.insert( + HeaderName::from_static("x-authz"), + HeaderValue::from_str(&format!("{}:tk:{}",res.session_resp.session.identity.id, res.session_resp.session_token)).unwrap_or(HeaderValue::from_static("")), + ); + ret_status = StatusCode::OK; html = format!( r#"

Login

@@ -190,39 +200,46 @@ pub async fn login_handler(header: HeaderMap, Extension(dbs): Extension
logout
Status: {}
"#, - &reqenv.websrvr_url(),res.session_resp.session.identity.id, res.session_resp.session_token, - &reqenv.websrvr_url(),res.session_resp.session.identity.id, res.session_resp.session_token, + &reqenv.public_url(),res.session_resp.session.identity.id, res.session_resp.session_token, + &reqenv.public_url(),res.session_resp.session.identity.id, res.session_resp.session_token, res.status); } else { + ret_status = StatusCode::OK; html = format!( r#"

Login

Status: {}
Registration
- "#,res.status,&reqenv.websrvr_url()); + "#,res.status,&reqenv.public_url()); } }, Err(e) => { eprintln!("{}",e); + // ret_status = StatusCode::FORBIDDEN; + ret_status = StatusCode::NOT_FOUND; html = format!( r#"

Login

Registration
Error: {}
- "#,&reqenv.websrvr_url(),e); + "#,&reqenv.public_url(),e); } } }, Err(e) => { eprintln!("{}",e); + ret_status = StatusCode::BAD_REQUEST; + // ret_status = StatusCode::UNAUTHORIZED; html = format!( r#"

Login

-
Error: {}
- "#,e); +
Error: No Login Found
+ "#); }, }; - Html(html) + (ret_status, headers, Html(html)) + // (StatusCode, HeaderMap, &'static str) + //Html(html) } pub async fn recovery_handler() -> Html<&'static str> { Html("

Hello, World!

") @@ -255,14 +272,14 @@ pub async fn whoami(id: String, token: String,
Status: {}
logout
"#,res.status, - &reqenv.websrvr_url(),id,token) + &reqenv.public_url(),id,token) } else { html = format!( r#"

whoami

Status: {}
login
- "#,res.status, &reqenv.websrvr_url()) + "#,res.status, &reqenv.public_url()) } }, Err(e) => { @@ -272,7 +289,7 @@ pub async fn whoami(id: String, token: String,

whoami

Login
Error: {}
- "#,&reqenv.websrvr_url(),e); + "#,&reqenv.public_url(),e); } } }, @@ -308,8 +325,9 @@ pub async fn whoami_handler(Path(id): Path, query: Query, println!("flow: {}",&reqid.flow); println!("csrf_token: {}",&reqid.csrf); println!("cookie: {}",&reqid.cookie); + let flow_logout = String::from("self-service/logout"); let query_id = format!("/api?{}&flow={}",&urlquery.token,&reqid.flow); - let reg_url=format!("{}/{}{}",&root_url,flow,query_id); + let reg_url=format!("{}/{}{}",&root_url,flow_logout,query_id); // match on_kratos_user(reqenv.websrvr().signin,&reqid.csrf,&id,®_url,user).await { match logout_kratos_user(&id,&urlquery.token.to_string(),reqenv.websrvr().signin,&req_cli,&reqid.csrf,&reqid.cookie,®_url).await { Ok(res) => { @@ -319,7 +337,7 @@ pub async fn whoami_handler(Path(id): Path, query: Query,

Logout

login
Status: {}
- "#,reqenv.websrvr_url(),res.status); + "#,reqenv.public_url(),res.status); }, Err(e) => { eprintln!("{}",e); diff --git a/src/handlers/router.rs b/src/handlers/router.rs index 30b8fc8..ea51a92 100644 --- a/src/handlers/router.rs +++ b/src/handlers/router.rs @@ -21,6 +21,15 @@ use std::{ use crate::defs::{DataDBs}; use crate::utils::reqenv::ReqEnv; +use app_env::{ + // AppStore, + // appenv::AppEnv, + // appinfo::AppInfo, + // appdata::AppData, + //config::{WebServer,SigninServer,SigninAuthzRoutes,AuthzMode}, + config::{SigninAuthzRoute,AuthzMode}, +}; + // pub async fn html_handler(Path(path): Path,query: Option>, Extension(dbs): Extension) -> Html<&'static str> { // pub async fn html_handler(Path(path): Path,Extension(dbs): Extension) -> Html<&'static str> { pub async fn html_handler(header: HeaderMap, Path(name): Path,Extension(dbs): Extension) -> Html<&'static str> { @@ -66,3 +75,32 @@ pub fn router_handlers(web_router: Router) -> Router { .route("/health/alive", get(alive_handler)) .route("/health/ready", get(ready_handler)) } +// These are to implement configuration defined signin authz routes +// using authz-route "path" ,"mode"(AuthMode) and "handler" +// +fn add_kratos_handler(web_router: Router,route: SigninAuthzRoute) -> Router { + let mut webrouter = web_router.to_owned(); + if route.path.is_empty() { + return webrouter; + } + match route.handler.as_str() { + // TODO Add handlers for Kratos. This should be moved from String to Enum type in SinginAuthRoute + "headers" => webrouter = webrouter.route(route.path.as_str(), get(crate::handlers::extauthz::authz_handler)), + _ => { + println!("Kratos signin authz route ({}) path ({}) with handler ({}) use authz_handler",route.id,route.path,route.handler); + webrouter = webrouter.route(route.path.as_str(), get(crate::handlers::extauthz::authz_handler)); + }, + }; + webrouter +} +pub fn router_authz_handlers(web_router: Router,signin_auth_routes: Vec) -> Router { + let mut webrouter = web_router.to_owned(); + for route in signin_auth_routes { + match route.mode { + AuthzMode::Kratos => webrouter = add_kratos_handler(webrouter,route), + // TODO Add new AuthMode + _ => eprintln!("Signin authz route {} undefined mode {}",route.id,route.mode), + } + } + webrouter +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b0feed9..f35a390 100644 --- a/src/main.rs +++ b/src/main.rs @@ -241,6 +241,7 @@ async fn up_web_server(webpos: usize) -> Result<()> { // WebSettings> { println!("Web services: done {} __________ ",chrono::Utc::now().timestamp()); } let mut web_router = Router::new(); + web_router = crate::handlers::router::router_authz_handlers(web_router,config.signin.authz_routes); web_router = crate::handlers::router::router_handlers(web_router); if !config.html_path.is_empty() { web_router = web_router.nest( diff --git a/src/utils/reqenv.rs b/src/utils/reqenv.rs index d50c6d6..c05ca0f 100644 --- a/src/utils/reqenv.rs +++ b/src/utils/reqenv.rs @@ -114,6 +114,15 @@ impl ReqEnv { let config=self.req.config().websrvrs[self.req.env().curr_web].to_owned(); format!("{}://{}:{}", config.srv_protocol, config.srv_host, config.srv_port) } + #[must_use] + pub fn public_url(&self) -> String { + let config=self.req.config().websrvrs[self.req.env().curr_web].to_owned(); + if config.public_url.is_empty() { + format!("{}://{}:{}", config.srv_protocol, config.srv_host, config.srv_port) + } else { + config.public_url + } + } #[must_use] pub fn module(&self) -> Module { self.req.module()