feat: 实现 Redfish API 标准接口;支持通过前端开关控制 Redfish 服务

This commit is contained in:
Fucheng Sha
2026-05-12 10:45:42 +08:00
parent 17cd74f64c
commit 4e8c342905
23 changed files with 2170 additions and 5 deletions

84
src/redfish/auth.rs Normal file
View File

@@ -0,0 +1,84 @@
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))
}