From 518a366ee287518c16a9ef4b27c3e9b586c4d875 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 25 Feb 2025 16:22:42 +0100 Subject: [PATCH] Make the admin API update the local policy data --- crates/cli/src/app_state.rs | 6 +++++ crates/handlers/src/admin/mod.rs | 4 ++++ crates/handlers/src/admin/v1/mod.rs | 4 ++++ .../handlers/src/admin/v1/policy_data/set.rs | 23 +++++++++++++++++-- crates/handlers/src/bin/api-schema.rs | 3 ++- crates/handlers/src/test_utils.rs | 6 +++++ docs/api/spec.json | 23 +++++++++++++++++++ 7 files changed, 66 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/app_state.rs b/crates/cli/src/app_state.rs index 6f8f059f0..4a1f89dee 100644 --- a/crates/cli/src/app_state.rs +++ b/crates/cli/src/app_state.rs @@ -204,6 +204,12 @@ impl FromRef for Limiter { } } +impl FromRef for Arc { + fn from_ref(input: &AppState) -> Self { + input.policy_factory.clone() + } +} + impl FromRef for BoxHomeserverConnection { fn from_ref(input: &AppState) -> Self { Box::new(input.homeserver_connection.clone()) diff --git a/crates/handlers/src/admin/mod.rs b/crates/handlers/src/admin/mod.rs index 4e65ba742..71e723a0c 100644 --- a/crates/handlers/src/admin/mod.rs +++ b/crates/handlers/src/admin/mod.rs @@ -4,6 +4,8 @@ // SPDX-License-Identifier: AGPL-3.0-only // Please see LICENSE in the repository root for full details. +use std::sync::Arc; + use aide::{ axum::ApiRouter, openapi::{OAuth2Flow, OAuth2Flows, OpenApi, SecurityScheme, Server, Tag}, @@ -20,6 +22,7 @@ use indexmap::IndexMap; use mas_axum_utils::FancyError; use mas_http::CorsLayerExt; use mas_matrix::BoxHomeserverConnection; +use mas_policy::PolicyFactory; use mas_router::{ ApiDoc, ApiDocCallback, OAuth2AuthorizationEndpoint, OAuth2TokenEndpoint, Route, SimpleRoute, UrlBuilder, @@ -118,6 +121,7 @@ where CallContext: FromRequestParts, Templates: FromRef, UrlBuilder: FromRef, + Arc: FromRef, { // We *always* want to explicitly set the possible responses, beacuse the // infered ones are not necessarily correct diff --git a/crates/handlers/src/admin/v1/mod.rs b/crates/handlers/src/admin/v1/mod.rs index ae258c842..590f65b58 100644 --- a/crates/handlers/src/admin/v1/mod.rs +++ b/crates/handlers/src/admin/v1/mod.rs @@ -4,12 +4,15 @@ // SPDX-License-Identifier: AGPL-3.0-only // Please see LICENSE in the repository root for full details. +use std::sync::Arc; + use aide::axum::{ ApiRouter, routing::{get_with, post_with}, }; use axum::extract::{FromRef, FromRequestParts}; use mas_matrix::BoxHomeserverConnection; +use mas_policy::PolicyFactory; use mas_storage::BoxRng; use super::call_context::CallContext; @@ -28,6 +31,7 @@ where S: Clone + Send + Sync + 'static, BoxHomeserverConnection: FromRef, PasswordManager: FromRef, + Arc: FromRef, BoxRng: FromRequestParts, CallContext: FromRequestParts, { diff --git a/crates/handlers/src/admin/v1/policy_data/set.rs b/crates/handlers/src/admin/v1/policy_data/set.rs index aa3edfe5a..b857b488b 100644 --- a/crates/handlers/src/admin/v1/policy_data/set.rs +++ b/crates/handlers/src/admin/v1/policy_data/set.rs @@ -2,9 +2,12 @@ // // SPDX-License-Identifier: AGPL-3.0-only +use std::sync::Arc; + use aide::{NoApi, OperationIo, transform::TransformOperation}; -use axum::{Json, response::IntoResponse}; +use axum::{Json, extract::State, response::IntoResponse}; use hyper::StatusCode; +use mas_policy::PolicyFactory; use mas_storage::BoxRng; use schemars::JsonSchema; use serde::Deserialize; @@ -21,6 +24,9 @@ use crate::{ #[derive(Debug, thiserror::Error, OperationIo)] #[aide(output_with = "Json")] pub enum RouteError { + #[error("Failed to instanciate policy with the provided data")] + InvalidPolicyData(#[from] mas_policy::LoadError), + #[error(transparent)] Internal(Box), } @@ -30,7 +36,10 @@ impl_from_error_for_route!(mas_storage::RepositoryError); impl IntoResponse for RouteError { fn into_response(self) -> axum::response::Response { let error = ErrorResponse::from_error(&self); - let status = StatusCode::INTERNAL_SERVER_ERROR; + let status = match self { + RouteError::InvalidPolicyData(_) => StatusCode::BAD_REQUEST, + RouteError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + }; (status, Json(error)).into_response() } } @@ -62,6 +71,12 @@ pub fn doc(operation: TransformOperation) -> TransformOperation { t.description("Policy data was successfully set") .example(response) }) + .response_with::<400, Json, _>(|t| { + let error = ErrorResponse::from_error(&RouteError::InvalidPolicyData( + mas_policy::LoadError::invalid_data_example(), + )); + t.description("Invalid policy data").example(error) + }) } #[tracing::instrument(name = "handler.admin.v1.policy_data.set", skip_all, err)] @@ -70,6 +85,7 @@ pub async fn handler( mut repo, clock, .. }: CallContext, NoApi(mut rng): NoApi, + State(policy_factory): State>, Json(request): Json, ) -> Result<(StatusCode, Json>), RouteError> { let policy_data = repo @@ -77,6 +93,9 @@ pub async fn handler( .set(&mut rng, &clock, request.data) .await?; + // Swap the policy data. This will fail if the policy data is invalid + policy_factory.set_dynamic_data(policy_data.clone()).await?; + repo.save().await?; Ok(( diff --git a/crates/handlers/src/bin/api-schema.rs b/crates/handlers/src/bin/api-schema.rs index 993719564..8005f5809 100644 --- a/crates/handlers/src/bin/api-schema.rs +++ b/crates/handlers/src/bin/api-schema.rs @@ -13,7 +13,7 @@ )] #![warn(clippy::pedantic)] -use std::io::Write; +use std::{io::Write, sync::Arc}; use aide::openapi::{Server, ServerVariable}; use indexmap::IndexMap; @@ -58,6 +58,7 @@ impl_from_ref!(mas_templates::Templates); impl_from_ref!(mas_matrix::BoxHomeserverConnection); impl_from_ref!(mas_keystore::Keystore); impl_from_ref!(mas_handlers::passwords::PasswordManager); +impl_from_ref!(Arc); fn main() -> Result<(), Box> { let (mut api, _) = mas_handlers::admin_api_router::(); diff --git a/crates/handlers/src/test_utils.rs b/crates/handlers/src/test_utils.rs index 96073301a..457f9bdca 100644 --- a/crates/handlers/src/test_utils.rs +++ b/crates/handlers/src/test_utils.rs @@ -513,6 +513,12 @@ impl FromRef for SiteConfig { } } +impl FromRef for Arc { + fn from_ref(input: &TestState) -> Self { + input.policy_factory.clone() + } +} + impl FromRef for BoxHomeserverConnection { fn from_ref(input: &TestState) -> Self { Box::new(input.homeserver_connection.clone()) diff --git a/docs/api/spec.json b/docs/api/spec.json index 1fcc17116..d14b2c3f8 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -640,6 +640,29 @@ } } } + }, + "400": { + "description": "Invalid policy data", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "example": { + "errors": [ + { + "title": "Failed to instanciate policy with the provided data" + }, + { + "title": "invalid policy data" + }, + { + "title": "Failed to merge policy data objects" + } + ] + } + } + } } } }