mithril_aggregator/http_server/routes/
certificate_routes.rs

1use warp::Filter;
2
3use crate::http_server::routes::middlewares;
4use crate::http_server::routes::router::RouterState;
5
6pub fn routes(
7    router_state: &RouterState,
8) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
9    certificate_certificates(router_state)
10        .or(certificate_genesis(router_state))
11        .or(certificate_certificate_hash(router_state))
12}
13
14/// GET /certificates
15fn certificate_certificates(
16    router_state: &RouterState,
17) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
18    warp::path!("certificates")
19        .and(warp::get())
20        .and(middlewares::with_logger(router_state))
21        .and(middlewares::with_http_message_service(router_state))
22        .and_then(handlers::certificate_certificates)
23}
24
25/// GET /certificate/genesis
26fn certificate_genesis(
27    router_state: &RouterState,
28) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
29    warp::path!("certificate" / "genesis")
30        .and(warp::get())
31        .and(middlewares::with_client_metadata(router_state))
32        .and(middlewares::with_logger(router_state))
33        .and(middlewares::with_http_message_service(router_state))
34        .and(middlewares::with_metrics_service(router_state))
35        .and_then(handlers::certificate_genesis)
36}
37
38/// GET /certificate/{certificate_hash}
39fn certificate_certificate_hash(
40    router_state: &RouterState,
41) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
42    warp::path!("certificate" / String)
43        .and(warp::get())
44        .and(middlewares::with_client_metadata(router_state))
45        .and(middlewares::with_logger(router_state))
46        .and(middlewares::with_http_message_service(router_state))
47        .and(middlewares::with_metrics_service(router_state))
48        .and_then(handlers::certificate_certificate_hash)
49}
50
51mod handlers {
52    use slog::{Logger, warn};
53    use std::convert::Infallible;
54    use std::sync::Arc;
55    use warp::http::StatusCode;
56
57    use crate::MetricsService;
58    use crate::http_server::routes::middlewares::ClientMetadata;
59    use crate::{http_server::routes::reply, services::MessageService};
60
61    pub const LIST_MAX_ITEMS: usize = 20;
62
63    /// List all Certificates
64    pub async fn certificate_certificates(
65        logger: Logger,
66        http_message_service: Arc<dyn MessageService>,
67    ) -> Result<impl warp::Reply, Infallible> {
68        match http_message_service
69            .get_certificate_list_message(LIST_MAX_ITEMS)
70            .await
71        {
72            Ok(certificates) => Ok(reply::json(&certificates, StatusCode::OK)),
73            Err(err) => {
74                warn!(logger,"certificate_certificates::error"; "error" => ?err);
75                Ok(reply::server_error(err))
76            }
77        }
78    }
79
80    /// Certificate by certificate hash
81    pub async fn certificate_genesis(
82        client_metadata: ClientMetadata,
83        logger: Logger,
84        http_message_service: Arc<dyn MessageService>,
85        metrics_service: Arc<MetricsService>,
86    ) -> Result<impl warp::Reply, Infallible> {
87        metrics_service
88            .get_certificate_detail_total_served_since_startup()
89            .increment(&[
90                client_metadata.origin_tag.as_deref().unwrap_or_default(),
91                client_metadata.client_type.as_deref().unwrap_or_default(),
92            ]);
93
94        match http_message_service.get_latest_genesis_certificate_message().await {
95            Ok(Some(certificate)) => Ok(reply::json(&certificate, StatusCode::OK)),
96            Ok(None) => Ok(reply::empty(StatusCode::NOT_FOUND)),
97            Err(err) => {
98                warn!(logger,"certificate_certificate_genesis::error"; "error" => ?err);
99                Ok(reply::server_error(err))
100            }
101        }
102    }
103
104    /// Certificate by certificate hash
105    pub async fn certificate_certificate_hash(
106        certificate_hash: String,
107        client_metadata: ClientMetadata,
108        logger: Logger,
109        http_message_service: Arc<dyn MessageService>,
110        metrics_service: Arc<MetricsService>,
111    ) -> Result<impl warp::Reply, Infallible> {
112        metrics_service
113            .get_certificate_detail_total_served_since_startup()
114            .increment(&[
115                client_metadata.origin_tag.as_deref().unwrap_or_default(),
116                client_metadata.client_type.as_deref().unwrap_or_default(),
117            ]);
118
119        match http_message_service.get_certificate_message(&certificate_hash).await {
120            Ok(Some(certificate)) => Ok(reply::json(&certificate, StatusCode::OK)),
121            Ok(None) => Ok(reply::empty(StatusCode::NOT_FOUND)),
122            Err(err) => {
123                warn!(logger,"certificate_certificate_hash::error"; "error" => ?err);
124                Ok(reply::server_error(err))
125            }
126        }
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use anyhow::anyhow;
133    use serde_json::Value::Null;
134    use std::sync::Arc;
135    use warp::{
136        http::{Method, StatusCode},
137        test::request,
138    };
139
140    use mithril_api_spec::APISpec;
141    use mithril_common::{
142        MITHRIL_CLIENT_TYPE_HEADER, MITHRIL_ORIGIN_TAG_HEADER, messages::CertificateMessage,
143        test_utils::fake_data,
144    };
145
146    use crate::{initialize_dependencies, services::MockMessageService};
147
148    use super::*;
149
150    fn setup_router(
151        state: RouterState,
152    ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
153        let cors = warp::cors()
154            .allow_any_origin()
155            .allow_headers(vec!["content-type"])
156            .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
157
158        warp::any().and(routes(&state).with(cors))
159    }
160
161    #[tokio::test]
162    async fn test_certificate_certificates_get_ok() {
163        let dependency_manager = initialize_dependencies!().await;
164        dependency_manager
165            .certificate_repository
166            .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
167            .await
168            .expect("certificate store save should have succeeded");
169
170        let method = Method::GET.as_str();
171        let path = "/certificates";
172
173        let response = request()
174            .method(method)
175            .path(path)
176            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
177                dependency_manager,
178            ))))
179            .await;
180
181        APISpec::verify_conformity(
182            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
183            method,
184            path,
185            "application/json",
186            &Null,
187            &response,
188            &StatusCode::OK,
189        )
190        .unwrap();
191    }
192
193    #[tokio::test]
194    async fn test_certificate_when_error_retrieving_certificates_returns_ko_500() {
195        let mut dependency_manager = initialize_dependencies!().await;
196        let mut message_service = MockMessageService::new();
197        message_service
198            .expect_get_certificate_list_message()
199            .returning(|_| Err(anyhow!("an error")));
200        dependency_manager.message_service = Arc::new(message_service);
201
202        let method = Method::GET.as_str();
203        let path = "/certificates";
204
205        let response = request()
206            .method(method)
207            .path(path)
208            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
209                dependency_manager,
210            ))))
211            .await;
212
213        APISpec::verify_conformity(
214            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
215            method,
216            path,
217            "application/json",
218            &Null,
219            &response,
220            &StatusCode::INTERNAL_SERVER_ERROR,
221        )
222        .unwrap();
223    }
224
225    #[tokio::test]
226    async fn test_certificate_certificate_hash_increments_certificate_detail_total_served_since_startup_metric()
227     {
228        let method = Method::GET.as_str();
229        let path = "/certificate/{certificate_hash}";
230
231        let dependency_manager = Arc::new(initialize_dependencies!().await);
232        let metric = dependency_manager
233            .metrics_service
234            .get_certificate_detail_total_served_since_startup();
235        let initial_counter_value_with_tag = metric.get(&["TEST", "CLI"]);
236        let initial_counter_value_with_unknown_tags =
237            metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"]);
238
239        request()
240            .method(method)
241            .path(path)
242            .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
243            .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
244            .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
245                dependency_manager.clone(),
246                &["TEST"],
247            )))
248            .await;
249
250        assert_eq!(
251            initial_counter_value_with_tag + 1,
252            metric.get(&["TEST", "CLI"])
253        );
254        assert_eq!(
255            initial_counter_value_with_unknown_tags,
256            metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"])
257        );
258    }
259
260    #[tokio::test]
261    async fn test_certificate_certificate_hash_get_ok() {
262        let dependency_manager = initialize_dependencies!().await;
263        dependency_manager
264            .certificate_repository
265            .create_certificate(fake_data::genesis_certificate("{certificate_hash}"))
266            .await
267            .expect("certificate store save should have succeeded");
268
269        let method = Method::GET.as_str();
270        let path = "/certificate/{certificate_hash}";
271
272        let response = request()
273            .method(method)
274            .path(path)
275            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
276                dependency_manager,
277            ))))
278            .await;
279
280        APISpec::verify_conformity(
281            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
282            method,
283            path,
284            "application/json",
285            &Null,
286            &response,
287            &StatusCode::OK,
288        )
289        .unwrap();
290    }
291
292    #[tokio::test]
293    async fn test_certificate_certificate_hash_get_ko_404() {
294        let dependency_manager = initialize_dependencies!().await;
295
296        let method = Method::GET.as_str();
297        let path = "/certificate/{certificate_hash}";
298
299        let response = request()
300            .method(method)
301            .path(path)
302            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
303                dependency_manager,
304            ))))
305            .await;
306
307        APISpec::verify_conformity(
308            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
309            method,
310            path,
311            "application/json",
312            &Null,
313            &response,
314            &StatusCode::NOT_FOUND,
315        )
316        .unwrap();
317    }
318
319    #[tokio::test]
320    async fn test_certificate_when_error_on_retrieving_certificate_hash_returns_ko_500() {
321        let mut dependency_manager = initialize_dependencies!().await;
322        let mut message_service = MockMessageService::new();
323        message_service
324            .expect_get_certificate_message()
325            .returning(|_| Err(anyhow!("an error")));
326        dependency_manager.message_service = Arc::new(message_service);
327
328        let method = Method::GET.as_str();
329        let path = "/certificate/{certificate_hash}";
330
331        let response = request()
332            .method(method)
333            .path(&path.replace("{certificate_hash}", "whatever"))
334            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
335                dependency_manager,
336            ))))
337            .await;
338
339        APISpec::verify_conformity(
340            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
341            method,
342            path,
343            "application/json",
344            &Null,
345            &response,
346            &StatusCode::INTERNAL_SERVER_ERROR,
347        )
348        .unwrap();
349    }
350
351    #[tokio::test]
352    async fn test_certificate_genesis_increments_certificate_detail_total_served_since_startup_metric()
353     {
354        let method = Method::GET.as_str();
355        let path = "/certificate/genesis";
356
357        let dependency_manager = Arc::new(initialize_dependencies!().await);
358        let metric = dependency_manager
359            .metrics_service
360            .get_certificate_detail_total_served_since_startup();
361        let initial_counter_value_with_tag = metric.get(&["TEST", "CLI"]);
362        let initial_counter_value_with_unknown_tags =
363            metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"]);
364
365        request()
366            .method(method)
367            .path(path)
368            .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
369            .header(MITHRIL_CLIENT_TYPE_HEADER, "CLI")
370            .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
371                dependency_manager.clone(),
372                &["TEST"],
373            )))
374            .await;
375
376        assert_eq!(
377            initial_counter_value_with_tag + 1,
378            metric.get(&["TEST", "CLI"])
379        );
380        assert_eq!(
381            initial_counter_value_with_unknown_tags,
382            metric.get(&["UNKNOWN_ORIGIN", "UNKNOWN_CLIENT_TYPE"])
383        );
384    }
385
386    #[tokio::test]
387    async fn test_certificate_genesis_get_ok() {
388        let dependency_manager = initialize_dependencies!().await;
389        dependency_manager
390            .certificate_repository
391            .create_certificate(fake_data::genesis_certificate("certificate_genesis_1"))
392            .await
393            .expect("certificate store save should have succeeded");
394        dependency_manager
395            .certificate_repository
396            .create_certificate(fake_data::genesis_certificate("certificate_genesis_2"))
397            .await
398            .expect("certificate store save should have succeeded");
399
400        let method = Method::GET.as_str();
401        let path = "/certificate/genesis";
402
403        let response = request()
404            .method(method)
405            .path(path)
406            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
407                dependency_manager,
408            ))))
409            .await;
410
411        APISpec::verify_conformity(
412            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
413            method,
414            path,
415            "application/json",
416            &Null,
417            &response,
418            &StatusCode::OK,
419        )
420        .unwrap();
421
422        let returned_genesis: CertificateMessage = serde_json::from_slice(response.body()).unwrap();
423        assert_eq!("certificate_genesis_2", &returned_genesis.hash);
424    }
425
426    #[tokio::test]
427    async fn test_certificate_genesis_get_ko_404() {
428        let dependency_manager = initialize_dependencies!().await;
429
430        let method = Method::GET.as_str();
431        let path = "/certificate/genesis";
432
433        let response = request()
434            .method(method)
435            .path(path)
436            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
437                dependency_manager,
438            ))))
439            .await;
440
441        APISpec::verify_conformity(
442            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
443            method,
444            path,
445            "application/json",
446            &Null,
447            &response,
448            &StatusCode::NOT_FOUND,
449        )
450        .unwrap();
451    }
452
453    #[tokio::test]
454    async fn test_certificate_when_error_on_retrieving_certificate_genesis_returns_ko_500() {
455        let mut dependency_manager = initialize_dependencies!().await;
456        let mut message_service = MockMessageService::new();
457        message_service
458            .expect_get_latest_genesis_certificate_message()
459            .returning(|| Err(anyhow!("an error")));
460        dependency_manager.message_service = Arc::new(message_service);
461
462        let method = Method::GET.as_str();
463        let path = "/certificate/genesis";
464
465        let response = request()
466            .method(method)
467            .path(path)
468            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
469                dependency_manager,
470            ))))
471            .await;
472
473        APISpec::verify_conformity(
474            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
475            method,
476            path,
477            "application/json",
478            &Null,
479            &response,
480            &StatusCode::INTERNAL_SERVER_ERROR,
481        )
482        .unwrap();
483    }
484}