Files
NLnetLabs-krill/src/daemon/http/dispatch/auth.rs
T
Martin HoffmannandGitHub 35cd5d0bb2 Redesign the HTTP service. (#1264)
Initially, this PR was intended to more clearly separate code between Krill
“business logic” – now bundled in a KrillManager –, and the HTTP server code
that serves the API. The former now lives in the server module, the latter
in the daemon module together with all the code to spin up a Krill daemon
driving the HTTP server.

However, along the way it turned into a complete redesign of how the HTTP
server code works. Request handling has been split into three stages that
forces implementers to check for permissions (or actively choose to not
check), and read the body (or check that there isn’t one). Dispatching of
the request has been restructured which should make it easier to follow what
goes on where.

This PR increases the minimum Rust version to 1.81.
2025-04-14 11:17:08 +02:00

132 lines
3.9 KiB
Rust

//! `/auth`
use hyper::Method;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /auth ---------------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("login") => login(request, path).await,
Some("logout") => logout(request, path).await,
#[cfg(feature = "multi-user")]
Some("callback") => multi_user::callback(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
async fn login(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
path.check_exhausted()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(server.authorizer().get_login_url().await?)
}
Method::POST => {
path.check_exhausted()?;
let (server, request) = request.proceed_raw();
Ok(HttpResponse::json(
&server.authorizer().login(&request).await?
))
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
async fn logout(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (server, request) = request.proceed_raw();
Ok(server.authorizer().logout(&request).await?)
}
#[cfg(feature = "multi-user")]
mod multi_user {
use base64::engine::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_ENGINE;
use log::trace;
use crate::commons::error::Error;
use crate::daemon::http::auth::LoggedInUser;
use crate::daemon::http::util::url_encode;
use super::*;
pub async fn callback(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
trace!(
"Authentication callback invoked: {:?}", &request.hyper()
);
let (server, request) = request.proceed_raw();
server.authorizer().login(&request).await.and_then(|user| {
build_auth_redirect_location(user).map_err(|err| {
Error::custom(format!(
"Unable to build redirect with logged in user details: \
{:?}",
err
))
})
}).map(|location| {
HttpResponse::found(&location)
}).or_else(|err| {
// HTTP redirects cannot have a response body and so we cannot
// render the error to be displayed in Lagosta as a JSON body,
// instead we must package the JSON as a query parameter.
let location = match serde_json::to_string(
&err.to_error_response()
) {
Ok(json) => {
format!("/ui/login?error={}", BASE64_ENGINE.encode(json))
}
Err(_) => String::from("/ui/login")
};
Ok(HttpResponse::found(&location))
})
}
fn build_auth_redirect_location(
user: LoggedInUser
) -> Result<String, Error> {
fn b64_encode_attributes_with_mapped_error(
a: &impl serde::Serialize,
) -> Result<String, Error> {
Ok(BASE64_ENGINE.encode(
serde_json::to_string(a)
.map_err(|err| Error::custom(err.to_string()))?,
))
}
let attributes = b64_encode_attributes_with_mapped_error(
user.attributes()
)?;
Ok(format!(
"/ui/login?token={}&id={}&attributes={}",
&url_encode(user.token())?,
&url_encode(user.id())?,
&url_encode(attributes)?,
))
}
}