From 051c3f9fb55a944ef8bc10d13b3a547090205870 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 14 May 2024 23:30:22 +0200 Subject: [PATCH] Fix multipart requests This patch fixes the implementation of multipart requests that were previously just ignored. Fixes: https://github.com/Nitrokey/nethsm-sdk-rs/issues/20 --- CHANGELOG.md | 8 ++ Cargo.toml | 3 + .../src/main/resources/crust/Cargo.mustache | 3 + src/apis/default_api.rs | 25 ++++- tests/basic.rs | 99 ++++++++++++++++++- 5 files changed, 135 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb54220..56d391f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Bugfixes + +- Fix multipart requests, namely `system_restore_post` ([#20](https://github.com/Nitrokey/nethsm-sdk-rs/issues/20)) + +[All Changes](https://github.com/Nitrokey/nethsm-sdk-rs/compare/v1.0.1...HEAD) + ## [v1.0.1](https://github.com/Nitrokey/nethsm-sdk-rs/releases/tag/v1.0.1) (2024-05-06) ### Bugfixes diff --git a/Cargo.toml b/Cargo.toml index d600ecf..e735c5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ exclude = [ ] [dependencies] +mime = "0.3" +multipart = { version = "0.18", default-features = false, features = ["client"] } serde = { default-features = false, version = "^1.0" } serde_derive = "^1.0" serde_json = { default-features = false, version = "^1.0" } @@ -27,6 +29,7 @@ ureq = { version = "2", features = ["json", "tls"], default-features = false } base64 = { version = "0.21", default-features = false, features = ["alloc"] } [dev-dependencies] +chrono = "0.4.38" env_logger = "0.11.3" rustainers = "0.12.0" rustls = { version = "0.22.4" } diff --git a/generator/src/main/resources/crust/Cargo.mustache b/generator/src/main/resources/crust/Cargo.mustache index 402254e..d51e3d1 100644 --- a/generator/src/main/resources/crust/Cargo.mustache +++ b/generator/src/main/resources/crust/Cargo.mustache @@ -41,8 +41,11 @@ serde_json = { default-features = false, version = "^1.0" } url = "^2.2" ureq = { version = "2", features = ["json", "tls"], default-features = false } base64 = { version = "0.21", default-features = false, features = ["alloc"] } +mime = "0.3" +multipart = { version = "0.18", default-features = false, features = ["client"] } [dev-dependencies] +chrono = "0.4.38" env_logger = "0.11.3" rustainers = "0.12.0" rustls = { version = "0.22.4" } diff --git a/src/apis/default_api.rs b/src/apis/default_api.rs index 39bf15b..e2543fd 100644 --- a/src/apis/default_api.rs +++ b/src/apis/default_api.rs @@ -3366,9 +3366,30 @@ pub fn system_restore_post( local_var_req_builder = local_var_req_builder.set("user-agent", local_var_user_agent); } - local_var_req_builder = local_var_req_builder.set("content-type", "multipart/form-data"); + let mut multipart = multipart::client::lazy::Multipart::new(); + if let Some(backup_file) = backup_file { + multipart.add_stream( + "backup_file", + std::io::Cursor::new(backup_file), + None::<&str>, + Some(mime::APPLICATION_OCTET_STREAM), + ); + } + if let Some(arguments) = arguments { + let arguments = serde_json::to_vec(&arguments).unwrap(); + multipart.add_stream( + "arguments", + std::io::Cursor::new(arguments), + None::<&str>, + Some(mime::APPLICATION_JSON), + ); + } + let multipart = multipart.prepare().unwrap(); - let local_var_resp = local_var_req_builder.call()?; + let mime_type = format!("multipart/form-data; boundary={}", multipart.boundary()); + local_var_req_builder = local_var_req_builder.set("content-type", &mime_type); + + let local_var_resp = local_var_req_builder.send(multipart)?; let local_var_headers = super::get_header_map(&local_var_resp); diff --git a/tests/basic.rs b/tests/basic.rs index 0442c94..2e17e6f 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,6 +1,16 @@ mod utils; -use nethsm_sdk_rs::apis::default_api; +use std::collections::BTreeSet; + +use chrono::Utc; +use nethsm_sdk_rs::{ + apis::{configuration::Configuration, default_api}, + models::{ + BackupPassphraseConfig, KeyGenerateRequestData, KeyMechanism, KeyType, + ProvisionRequestData, RestoreRequestArguments, SystemState, UnlockRequestData, + UserPostData, UserRole, + }, +}; #[tokio::test] async fn test_health_state() { @@ -10,3 +20,90 @@ async fn test_health_state() { }) .await } + +#[tokio::test] +async fn test_restore_unprovisioned() { + let admin_passphrase = "adminadmin"; + let backup_passphrase = "backupbackup"; + let unlock_passphrase = "unlockunlock"; + + let (generated_keys, backup) = utils::with_container(|mut config| { + let request = ProvisionRequestData { + unlock_passphrase: unlock_passphrase.to_owned(), + admin_passphrase: admin_passphrase.to_owned(), + system_time: Utc::now().to_rfc3339(), + }; + default_api::provision_post(&config, request).unwrap(); + + config.basic_auth = Some(("admin".to_owned(), Some(admin_passphrase.to_owned()))); + + let request = KeyGenerateRequestData { + r#type: KeyType::Rsa, + length: Some(2048), + mechanisms: vec![KeyMechanism::RsaDecryptionRaw], + ..Default::default() + }; + let key_id = default_api::keys_generate_post(&config, request) + .unwrap() + .entity + .id; + let keys = BTreeSet::from([key_id]); + + assert_eq!(list_keys(&config), keys); + + let request = BackupPassphraseConfig { + new_passphrase: backup_passphrase.to_owned(), + current_passphrase: String::new(), + }; + default_api::config_backup_passphrase_put(&config, request).unwrap(); + + let request = UserPostData { + real_name: "Backup User".to_owned(), + role: UserRole::Backup, + passphrase: backup_passphrase.to_owned(), + }; + default_api::users_user_id_put(&config, "backup", request).unwrap(); + + config.basic_auth = Some(("backup".to_owned(), Some(backup_passphrase.to_owned()))); + + let backup = default_api::system_backup_post(&config).unwrap().entity; + + (keys, backup) + }) + .await; + + let restored_keys = utils::with_container(|mut config| { + let state = default_api::health_state_get(&config).unwrap().entity.state; + assert_eq!(state, SystemState::Unprovisioned); + + let request = RestoreRequestArguments { + backup_passphrase: Some(backup_passphrase.to_owned()), + system_time: Some(Utc::now().to_rfc3339()), + }; + default_api::system_restore_post(&config, Some(request), Some(backup)).unwrap(); + + let state = default_api::health_state_get(&config).unwrap().entity.state; + assert_eq!(state, SystemState::Locked); + + let request = UnlockRequestData { + passphrase: unlock_passphrase.to_owned(), + }; + default_api::unlock_post(&config, request).unwrap(); + + config.basic_auth = Some(("admin".to_owned(), Some(admin_passphrase.to_owned()))); + + list_keys(&config) + }) + .await; + + assert_eq!(generated_keys, restored_keys); +} + +fn list_keys(config: &Configuration) -> BTreeSet { + default_api::keys_get(&config, None) + .unwrap() + .entity + .into_iter() + .map(|item| item.id) + .collect() +}