diff --git a/README.md b/README.md index d0f04dd1..69797a42 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,9 @@ Currently we only provide an API for view the current state: | Resource | Method | Action | | ------------------------------ | -------- | ------------------------------- | | /publishers | Get | List all current publishers | -| /publishers/{number}/response | Get | Get [repository response xml](https://tools.ietf.org/html/rfc8183#section-5.2.4)| +| /publishers/{handle} | Get | Get publisher details | +| /publishers/{handle}/id.cer | Get | Get publisher id certificate | +| /publishers/{handle}/response-xml | Get | Get [repository response xml](https://tools.ietf.org/html/rfc8183#section-5.2.4)| For the moment publishers are configured by adding the publisher's ['publisher @@ -79,7 +81,9 @@ logic wrapping around the API to ensure that things are then synchronised. ## UI -To add static resources, add to the 'static' folder and include static mapping at the end of src/pubd/httpd.rs. You should be able to get to them if you restart the server. +To add static resources, add to the 'static' folder and include static +mapping at the end of src/pubd/httpd.rs. You should be able to get to them if +you restart the server. diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 00000000..d9f45a94 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,123 @@ +//! Support for the Json API + +use std::sync::Arc; +use crate::ext_serde; +use crate::provisioning::publisher::Publisher; +use crate::rpki::uri; + +//------------ Link ---------------------------------------------------------- + +/// Defines a link element to include as part of a links array in a Json +/// response. +#[derive(Clone, Debug, Serialize)] +pub struct Link<'a> { + rel: &'a str, + link: String +} + + +//------------ PublisherSummaryInfo ------------------------------------------ + +/// Defines a summary of publisher information to be used in the publisher +/// list. +#[derive(Clone, Debug, Serialize)] +pub struct PublisherSummaryInfo<'a> { + id: &'a str, + links: Vec> +} + +impl<'a> PublisherSummaryInfo<'a> { + pub fn from( + publisher: &'a Publisher, + path_publishers: &'a str + ) -> PublisherSummaryInfo<'a> { + let id = publisher.name().as_str(); + let mut links = Vec::new(); + + let response_link = Link { + rel: "response-xml", + link: format!("{}/{}/response-xml", path_publishers, id) + }; + let self_link = Link { + rel: "self", + link: format!("{}/{}", path_publishers, id) + }; + + links.push(response_link); + links.push(self_link); + + PublisherSummaryInfo { + id, + links + } + } +} + + +//------------ PublisherList ------------------------------------------------- + +/// This type represents a list of (all) current publishers to show in the API +#[derive(Clone, Debug, Serialize)] +pub struct PublisherList<'a> { + publishers: Vec> +} + +impl<'a> PublisherList<'a> { + pub fn from( + publishers: &'a Vec>, + path_publishers: &'a str + ) -> PublisherList<'a> { + let publishers: Vec = publishers.iter().map(|p| + PublisherSummaryInfo::from(&p, path_publishers) + ).collect(); + + PublisherList { + publishers + } + } + + pub fn publishers(&self) -> &Vec { + &self.publishers + } +} + + +//------------ PublisherDetails ---------------------------------------------- + +#[derive(Clone, Debug, Serialize)] +pub struct PublisherDetails<'a> { + name: &'a str, + #[serde(serialize_with = "ext_serde::ser_rsync_uri")] + base_uri: &'a uri::Rsync, + #[serde(serialize_with = "ext_serde::ser_http_uri")] + service_uri: &'a uri::Http, + links: Vec> +} + +impl<'a> PublisherDetails<'a> { + pub fn from( + publisher: &'a Arc, + path_publishers: &'a str + ) -> PublisherDetails<'a> { + let name = publisher.name().as_str(); + let base_uri = publisher.base_uri(); + let service_uri = publisher.service_uri(); + + let mut links = Vec::new(); + links.push(Link { + rel: "response-xml", + link: format!("{}/{}/response-xml", path_publishers, name) + }); + links.push(Link { + rel: "id-cert", + link: format!("{}/{}/id.cer", path_publishers, name) + }); + + PublisherDetails { + name, + base_uri, + service_uri, + links + } + } +} diff --git a/src/lib.rs b/src/lib.rs index b11585b8..8cec1251 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,6 +26,7 @@ extern crate xml as xmlrs; extern crate ring; extern crate untrusted; +pub mod api; pub mod file; pub mod provisioning; pub mod pubc; diff --git a/src/pubd/http.rs b/src/pubd/http.rs index 6b7e5c81..f83a7836 100644 --- a/src/pubd/http.rs +++ b/src/pubd/http.rs @@ -18,9 +18,13 @@ use crate::pubd::https; use crate::pubd::pubserver; use crate::pubd::pubserver::PubServer; use crate::remote::sigmsg::SignedMessage; +use api::PublisherList; +use api::PublisherDetails; const NOT_FOUND: &'static [u8] = include_bytes!("../../static/html/404.html"); +const PATH_PUBLISHERS: &'static str = "/api/v1/publishers"; + //------------ PubServerApp -------------------------------------------------- pub struct PubServerApp(App>>); @@ -31,9 +35,15 @@ impl PubServerApp { pub fn new(server: Arc>) -> Self { let app = App::with_state(server) .middleware(middleware::Logger::default()) - .resource("/api/v1/publishers", |r| { + .resource(PATH_PUBLISHERS, |r| { r.f(Self::publishers) }) + .resource("/api/v1/publishers/{handle}", |r| { + r.f(Self::publisher_details) + }) + .resource("/api/v1/publishers/{handle}/id.cer", |r| { + r.f(Self::id_cert) + }) .resource("/api/v1/publishers/{handle}/response-xml", |r| { r.f(Self::repository_response) }) @@ -156,7 +166,51 @@ impl PubServerApp { let server: RwLockReadGuard = req.state().read().unwrap(); match server.publishers() { Err(e) => Self::server_error(Error::ServerError(e)), - Ok(publishers) => Self::render_json(publishers) + Ok(publishers) => { + Self::render_json( + PublisherList::from(&publishers, PATH_PUBLISHERS) + ) + } + } + } + + /// Returns a json structure with publisher details + fn publisher_details(req: &HttpRequest) -> HttpResponse { + let server: RwLockReadGuard = req.state().read().unwrap(); + match req.match_info().get("handle") { + None => Self::p404(req), + Some(handle) => { + match server.publisher(handle) { + Ok(None) => Self::p404(req), + Ok(Some(publisher)) => { + Self::render_json( + PublisherDetails::from(&publisher, + PATH_PUBLISHERS) + ) + }, + Err(e) => Self::server_error(Error::ServerError(e)) + } + } + } + } + + /// Returns the id.cer for a publisher + fn id_cert(req: &HttpRequest) -> HttpResponse { + let server: RwLockReadGuard = req.state().read().unwrap(); + match req.match_info().get("handle") { + None => Self::p404(req), + Some(handle) => { + match server.publisher(handle) { + Ok(None) => Self::p404(req), + Ok(Some(publisher)) => { + let bytes = publisher.id_cert().to_bytes(); + HttpResponse::Ok() + .content_type("application/pkix-cert") + .body(bytes) + }, + Err(e) => Self::server_error(Error::ServerError(e)) + } + } } } @@ -169,7 +223,9 @@ impl PubServerApp { Some(handle) => { match server.repository_response(handle) { Ok(res) => { - HttpResponse::Ok().body(res.encode_vec()) + HttpResponse::Ok() + .content_type("application/xml") + .body(res.encode_vec()) }, Err(pubserver::Error::PublisherStoreError (publisher_store::Error::UnknownPublisher(_))) => { @@ -262,7 +318,9 @@ impl PubServerApp { fn render_json(object: O) -> HttpResponse { match serde_json::to_string(&object){ Ok(enc) => { - HttpResponse::Ok().body(enc) + HttpResponse::Ok() + .content_type("application/json") + .body(enc) }, Err(e) => Self::server_error(Error::JsonError(e)) } diff --git a/src/pubd/pubserver.rs b/src/pubd/pubserver.rs index 445807b6..be4ae523 100644 --- a/src/pubd/pubserver.rs +++ b/src/pubd/pubserver.rs @@ -108,6 +108,15 @@ impl PubServer { .map_err(|e| { Error::PublisherStoreError(e) }) } + /// Returns an option for a publisher. + pub fn publisher( + &self, + publisher_name: &str + ) -> Result>, Error> { + self.publisher_store.publisher(publisher_name) + .map_err(|e| Error::PublisherStoreError(e)) + } + /// Returns a repository response for the given publisher. /// /// Returns an error if the publisher is unknown. diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 57af6e26..221fede1 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -19,7 +19,6 @@ use rpubd::remote::oob::exchange::PublisherRequest; use rpubd::remote::oob::exchange::RepositoryResponse; use rpubd::file; use rpubd::file::CurrentFile; -use rpubd::provisioning::publisher::Publisher; use rpubd::pubc::client::PubClient; use rpubd::pubd::config::Config; use rpubd::pubd::http::PubServerApp; @@ -68,13 +67,11 @@ fn client_publish_at_server() { // XXX TODO: Find a better way to know the server is ready! thread::sleep(time::Duration::from_millis(500)); - // Should see one configured publisher - let mut res = reqwest::get("http://localhost:3000/publishers").unwrap(); - let pl: Vec = serde_json::from_str(&res.text().unwrap()).unwrap(); - assert_eq!(1, pl.len()); - // Should get repository response for alice - let mut res = reqwest::get("http://localhost:3000/publishers/alice").unwrap(); + let mut res = reqwest::get + ("http://localhost:3000/api/v1/publishers/alice/response-xml") + .unwrap(); + let repo_res = RepositoryResponse::decode( res.text().unwrap().as_bytes() ).unwrap();