mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-12 18:44:25 +08:00
107 lines
3.6 KiB
Rust
107 lines
3.6 KiB
Rust
use axum::{
|
|
extract::{Request, State},
|
|
http::{header, Method, StatusCode},
|
|
middleware::Next,
|
|
response::{IntoResponse, Response},
|
|
};
|
|
use base64::Engine;
|
|
use std::sync::Arc;
|
|
|
|
use super::schema::RedfishError;
|
|
use crate::state::AppState;
|
|
|
|
pub async fn redfish_auth_middleware(
|
|
State(state): State<Arc<AppState>>,
|
|
mut request: Request,
|
|
next: Next,
|
|
) -> Response {
|
|
if !state.config.is_initialized() {
|
|
let body = RedfishError::service_unavailable("System not initialized");
|
|
return (StatusCode::SERVICE_UNAVAILABLE, axum::Json(body)).into_response();
|
|
}
|
|
|
|
let path = request.uri().path();
|
|
if is_redfish_public_endpoint(path, request.method()) {
|
|
return next.run(request).await;
|
|
}
|
|
|
|
if let Some(token) = request.headers().get("X-Auth-Token") {
|
|
if let Ok(token_str) = token.to_str() {
|
|
if state.is_session_revoked(token_str).await {
|
|
let body = RedfishError::invalid_credentials();
|
|
return (StatusCode::UNAUTHORIZED, axum::Json(body)).into_response();
|
|
}
|
|
if let Ok(Some(session)) = state.sessions.get(token_str).await {
|
|
request.extensions_mut().insert(session);
|
|
return next.run(request).await;
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(auth_header) = request.headers().get(header::AUTHORIZATION) {
|
|
if let Ok(auth_str) = auth_header.to_str() {
|
|
if let Some(credentials) = auth_str.strip_prefix("Basic ") {
|
|
if let Some((username, password)) = decode_basic_auth(credentials) {
|
|
match state.users.verify(&username, &password).await {
|
|
Ok(Some(user)) => {
|
|
request.extensions_mut().insert(user);
|
|
return next.run(request).await;
|
|
}
|
|
_ => {
|
|
let body = RedfishError::invalid_credentials();
|
|
return (StatusCode::UNAUTHORIZED, axum::Json(body)).into_response();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let body = RedfishError::authentication_required();
|
|
(StatusCode::UNAUTHORIZED, axum::Json(body)).into_response()
|
|
}
|
|
|
|
fn is_redfish_public_endpoint(path: &str, method: &Method) -> bool {
|
|
matches!(path, "/" | "/v1" | "/v1/" | "/v1/odata")
|
|
|| path.starts_with("/v1/$metadata")
|
|
|| (path == "/v1/SessionService/Sessions" && *method == Method::POST)
|
|
}
|
|
|
|
fn decode_basic_auth(encoded: &str) -> Option<(String, String)> {
|
|
let decoded = base64::engine::general_purpose::STANDARD
|
|
.decode(encoded)
|
|
.ok()?;
|
|
let credentials = String::from_utf8(decoded).ok()?;
|
|
let mut parts = credentials.splitn(2, ':');
|
|
let username = parts.next()?.to_string();
|
|
let password = parts.next()?.to_string();
|
|
if username.is_empty() {
|
|
return None;
|
|
}
|
|
Some((username, password))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn only_service_discovery_and_session_creation_are_public() {
|
|
assert!(is_redfish_public_endpoint("/v1/", &Method::GET));
|
|
assert!(is_redfish_public_endpoint("/v1/$metadata", &Method::GET));
|
|
assert!(is_redfish_public_endpoint(
|
|
"/v1/SessionService/Sessions",
|
|
&Method::POST
|
|
));
|
|
|
|
assert!(!is_redfish_public_endpoint(
|
|
"/v1/Managers/1/VirtualMedia",
|
|
&Method::GET
|
|
));
|
|
assert!(!is_redfish_public_endpoint(
|
|
"/v1/Managers/1/VirtualMedia/1/Actions/VirtualMedia.EjectMedia",
|
|
&Method::POST
|
|
));
|
|
}
|
|
}
|