Add TCP handlers for opaque protocol

This commit is contained in:
Valentin Tolmer 2021-06-21 09:24:49 +02:00 committed by nitnelave
parent 7be0e420d4
commit 4d68a2a015
3 changed files with 102 additions and 25 deletions

View File

@ -4,11 +4,11 @@ use crate::{
opaque_handler::OpaqueHandler, opaque_handler::OpaqueHandler,
}, },
infra::{ infra::{
tcp_api::{error_to_api_response, ApiResult},
tcp_backend_handler::*, tcp_backend_handler::*,
tcp_server::{error_to_http_response, AppState}, tcp_server::{error_to_http_response, AppState},
}, },
}; };
use lldap_model::{JWTClaims, BindRequest};
use actix_web::{ use actix_web::{
cookie::{Cookie, SameSite}, cookie::{Cookie, SameSite},
dev::{Service, ServiceRequest, ServiceResponse, Transform}, dev::{Service, ServiceRequest, ServiceResponse, Transform},
@ -22,6 +22,7 @@ use futures::future::{ok, Ready};
use futures_util::{FutureExt, TryFutureExt}; use futures_util::{FutureExt, TryFutureExt};
use hmac::Hmac; use hmac::Hmac;
use jwt::{SignWithKey, VerifyWithKey}; use jwt::{SignWithKey, VerifyWithKey};
use lldap_model::{login, registration, BindRequest, JWTClaims};
use log::*; use log::*;
use sha2::Sha512; use sha2::Sha512;
use std::collections::{hash_map::DefaultHasher, HashSet}; use std::collections::{hash_map::DefaultHasher, HashSet};
@ -165,30 +166,35 @@ where
.finish() .finish()
} }
async fn post_authorize<Backend>( async fn opaque_login_start<Backend>(
data: web::Data<AppState<Backend>>, data: web::Data<AppState<Backend>>,
request: web::Json<BindRequest>, request: web::Json<login::ClientLoginStartRequest>,
) -> ApiResult<login::ServerLoginStartResponse>
where
Backend: OpaqueHandler + 'static,
{
data.backend_handler
.login_start(request.clone())
.await
.map(|res| ApiResult::Left(web::Json(res)))
.unwrap_or_else(error_to_api_response)
}
async fn get_login_successful_response<Backend>(
data: &web::Data<AppState<Backend>>,
name: &str,
) -> HttpResponse ) -> HttpResponse
where where
Backend: TcpBackendHandler + BackendHandler + LoginHandler + 'static, Backend: TcpBackendHandler + BackendHandler,
{ {
let req: BindRequest = request.clone(); // The authentication was successful, we need to fetch the groups to create the JWT
// token.
data.backend_handler data.backend_handler
.bind(req) .get_user_groups(name.to_string())
// If the authentication was successful, we need to fetch the groups to create the JWT .and_then(|g| async { Ok((g, data.backend_handler.create_refresh_token(&name).await?)) })
// token.
.and_then(|_| data.backend_handler.get_user_groups(request.name.clone()))
.and_then(|g| async {
Ok((
g,
data.backend_handler
.create_refresh_token(&request.name)
.await?,
))
})
.await .await
.map(|(groups, (refresh_token, max_age))| { .map(|(groups, (refresh_token, max_age))| {
let token = create_jwt(&data.jwt_key, request.name.clone(), groups); let token = create_jwt(&data.jwt_key, name.to_string(), groups);
HttpResponse::Ok() HttpResponse::Ok()
.cookie( .cookie(
Cookie::build("token", token.as_str()) Cookie::build("token", token.as_str())
@ -199,7 +205,7 @@ where
.finish(), .finish(),
) )
.cookie( .cookie(
Cookie::build("refresh_token", refresh_token + "+" + &request.name) Cookie::build("refresh_token", refresh_token + "+" + &name)
.max_age(max_age.num_days().days()) .max_age(max_age.num_days().days())
.path("/auth") .path("/auth")
.http_only(true) .http_only(true)
@ -211,6 +217,64 @@ where
.unwrap_or_else(error_to_http_response) .unwrap_or_else(error_to_http_response)
} }
async fn opaque_login_finish<Backend>(
data: web::Data<AppState<Backend>>,
request: web::Json<login::ClientLoginFinishRequest>,
) -> HttpResponse
where
Backend: TcpBackendHandler + BackendHandler + OpaqueHandler + 'static,
{
let name = match data.backend_handler.login_finish(request.clone()).await {
Ok(n) => n,
Err(e) => return error_to_http_response(e),
};
get_login_successful_response(&data, &name).await
}
async fn post_authorize<Backend>(
data: web::Data<AppState<Backend>>,
request: web::Json<BindRequest>,
) -> HttpResponse
where
Backend: TcpBackendHandler + BackendHandler + LoginHandler + 'static,
{
if let Err(e) = data.backend_handler.bind(request.clone()).await {
return error_to_http_response(e);
}
get_login_successful_response(&data, &request.name).await
}
async fn opaque_register_start<Backend>(
data: web::Data<AppState<Backend>>,
request: web::Json<registration::ClientRegistrationStartRequest>,
) -> ApiResult<registration::ServerRegistrationStartResponse>
where
Backend: OpaqueHandler + 'static,
{
data.backend_handler
.registration_start(request.clone())
.await
.map(|res| ApiResult::Left(web::Json(res)))
.unwrap_or_else(error_to_api_response)
}
async fn opaque_register_finish<Backend>(
data: web::Data<AppState<Backend>>,
request: web::Json<registration::ClientRegistrationFinishRequest>,
) -> HttpResponse
where
Backend: TcpBackendHandler + BackendHandler + OpaqueHandler + 'static,
{
if let Err(e) = data
.backend_handler
.registration_finish(request.clone())
.await
{
return error_to_http_response(e);
}
HttpResponse::Ok().finish()
}
pub struct CookieToHeaderTranslatorFactory; pub struct CookieToHeaderTranslatorFactory;
impl<S> Transform<S, ServiceRequest> for CookieToHeaderTranslatorFactory impl<S> Transform<S, ServiceRequest> for CookieToHeaderTranslatorFactory
@ -306,6 +370,22 @@ where
Backend: TcpBackendHandler + LoginHandler + OpaqueHandler + BackendHandler + 'static, Backend: TcpBackendHandler + LoginHandler + OpaqueHandler + BackendHandler + 'static,
{ {
cfg.service(web::resource("").route(web::post().to(post_authorize::<Backend>))) cfg.service(web::resource("").route(web::post().to(post_authorize::<Backend>)))
.service(
web::resource("/opaque/login/start")
.route(web::post().to(opaque_login_start::<Backend>)),
)
.service(
web::resource("/opaque/login/finish")
.route(web::post().to(opaque_login_finish::<Backend>)),
)
.service(
web::resource("/opaque/register/start")
.route(web::post().to(opaque_register_start::<Backend>)),
)
.service(
web::resource("/opaque/register/finish")
.route(web::post().to(opaque_register_finish::<Backend>)),
)
.service(web::resource("/refresh").route(web::get().to(get_refresh::<Backend>))) .service(web::resource("/refresh").route(web::get().to(get_refresh::<Backend>)))
.service(web::resource("/logout").route(web::post().to(post_logout::<Backend>))); .service(web::resource("/logout").route(web::post().to(post_logout::<Backend>)));
} }

View File

@ -7,11 +7,11 @@ use crate::{
}; };
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
fn error_to_api_response<T>(error: DomainError) -> ApiResult<T> { pub(crate) fn error_to_api_response<T>(error: DomainError) -> ApiResult<T> {
ApiResult::Right(error_to_http_response(error)) ApiResult::Right(error_to_http_response(error))
} }
type ApiResult<M> = actix_web::Either<web::Json<M>, HttpResponse>; pub type ApiResult<M> = actix_web::Either<web::Json<M>, HttpResponse>;
async fn user_list_handler<Backend>( async fn user_list_handler<Backend>(
data: web::Data<AppState<Backend>>, data: web::Data<AppState<Backend>>,

View File

@ -73,10 +73,7 @@ fn http_config<Backend>(
.service(web::scope("/").route("/.*", web::get().to(index))); .service(web::scope("/").route("/.*", web::get().to(index)));
} }
pub(crate) struct AppState<Backend> pub(crate) struct AppState<Backend> {
where
Backend: TcpBackendHandler + BackendHandler + 'static,
{
pub backend_handler: Backend, pub backend_handler: Backend,
pub jwt_key: Hmac<Sha512>, pub jwt_key: Hmac<Sha512>,
pub jwt_blacklist: RwLock<HashSet<u64>>, pub jwt_blacklist: RwLock<HashSet<u64>>,