manta_server/service/
redfish.rs

1//! Redfish-endpoint queries and CRUD operations.
2
3use manta_backend_dispatcher::error::Error;
4use manta_backend_dispatcher::interfaces::hsm::redfish_endpoint::RedfishEndpointTrait;
5use manta_backend_dispatcher::types::hsm::inventory::{
6  RedfishEndpoint, RedfishEndpointArray,
7};
8
9use crate::{
10  server::common::{app_context::InfraContext, jwt_ops},
11  service::authorization::validate_user_group_members_access,
12};
13pub use manta_shared::types::api::redfish_endpoints::{
14  GetRedfishEndpointsParams, UpdateRedfishEndpointParams,
15};
16
17/// Convert a `UpdateRedfishEndpointParams` (CLI/HTTP wire shape) into a
18/// backend [`RedfishEndpoint`] suitable for `add_redfish_endpoint` /
19/// `update_redfish_endpoint`. Pure mapping — no I/O.
20pub(crate) fn params_to_redfish_endpoint(
21  params: UpdateRedfishEndpointParams,
22) -> RedfishEndpoint {
23  RedfishEndpoint {
24    id: params.id,
25    name: params.name,
26    hostname: params.hostname,
27    domain: params.domain,
28    fqdn: params.fqdn,
29    enabled: Some(params.enabled),
30    user: params.user,
31    password: params.password,
32    use_ssdp: Some(params.use_ssdp),
33    mac_required: Some(params.mac_required),
34    mac_addr: params.mac_addr,
35    ip_address: params.ip_address,
36    rediscover_on_update: Some(params.rediscover_on_update),
37    template_id: params.template_id,
38    r#type: None,
39    uuid: None,
40    discovery_info: None,
41  }
42}
43
44/// List Redfish endpoint registrations, applying any caller-supplied
45/// filters (`id` / `fqdn` / `uuid` / `macaddr` / `ipaddress`).
46///
47/// Authorization rules:
48/// - Admin tokens (carrying [`crate::service::authorization::PA_ADMIN`])
49///   may list every endpoint, with or without filters.
50/// - Non-admin callers MUST scope the request by `id`. The xname is
51///   then validated against the caller's accessible groups; without
52///   an `id`, the response could leak every BMC's identity and
53///   credentials. The non-admin broad listing returns `BadRequest`.
54pub async fn get_redfish_endpoints(
55  infra: &InfraContext<'_>,
56  token: &str,
57  params: &GetRedfishEndpointsParams,
58) -> Result<RedfishEndpointArray, Error> {
59  tracing::info!("Get Redfish endpoints");
60
61  if !jwt_ops::is_user_admin(token) {
62    let Some(xname) = params.id.as_deref() else {
63      return Err(Error::BadRequest(
64        "Non-admin callers must scope a Redfish-endpoints query by `id`."
65          .to_string(),
66      ));
67    };
68    validate_user_group_members_access(infra, token, &[xname.to_string()])
69      .await?;
70  }
71
72  infra
73    .backend
74    .get_redfish_endpoints(
75      token,
76      params.id.as_deref(),
77      params.fqdn.as_deref(),
78      None,
79      params.uuid.as_deref(),
80      params.macaddr.as_deref(),
81      params.ipaddress.as_deref(),
82      None,
83    )
84    .await
85}
86
87/// Register a new Redfish endpoint with HSM.
88///
89/// The caller-supplied `UpdateRedfishEndpointParams` is converted to a
90/// single-element `RedfishEndpointArray` before reaching the backend.
91pub async fn add_redfish_endpoint(
92  infra: &InfraContext<'_>,
93  token: &str,
94  params: UpdateRedfishEndpointParams,
95) -> Result<(), Error> {
96  tracing::info!("Add Redfish endpoint id={}", params.id);
97
98  validate_user_group_members_access(
99    infra,
100    token,
101    std::slice::from_ref(&params.id),
102  )
103  .await?;
104
105  let endpoint = params_to_redfish_endpoint(params);
106  let array = RedfishEndpointArray {
107    redfish_endpoints: Some(vec![endpoint]),
108  };
109  infra.backend.add_redfish_endpoint(token, &array).await
110}
111
112/// Update an existing Redfish endpoint's properties.
113///
114/// All fields on `UpdateRedfishEndpointParams` are written; partial
115/// updates aren't supported by the backend contract.
116pub async fn update_redfish_endpoint(
117  infra: &InfraContext<'_>,
118  token: &str,
119  params: UpdateRedfishEndpointParams,
120) -> Result<(), Error> {
121  tracing::info!("Update Redfish endpoint id={}", params.id);
122
123  validate_user_group_members_access(
124    infra,
125    token,
126    std::slice::from_ref(&params.id),
127  )
128  .await?;
129
130  let endpoint = params_to_redfish_endpoint(params);
131  infra
132    .backend
133    .update_redfish_endpoint(token, &endpoint)
134    .await
135}
136
137/// Delete a Redfish endpoint registration by id (BMC xname).
138///
139/// `NotFound` is surfaced by the backend when `id` does not match an
140/// existing registration; the service forwards it unchanged.
141pub async fn delete_redfish_endpoint(
142  infra: &InfraContext<'_>,
143  token: &str,
144  id: &str,
145) -> Result<(), Error> {
146  tracing::info!("Delete Redfish endpoint id={}", id);
147
148  validate_user_group_members_access(infra, token, &[id.to_string()]).await?;
149
150  infra
151    .backend
152    .delete_redfish_endpoint(token, id)
153    .await
154    .map(|_| ())
155}