diff --git a/crates/bitwarden/src/admin_console/auth_requests/approve.rs b/crates/bitwarden/src/admin_console/auth_requests/approve.rs index c8a0c01a1..f594c9963 100644 --- a/crates/bitwarden/src/admin_console/auth_requests/approve.rs +++ b/crates/bitwarden/src/admin_console/auth_requests/approve.rs @@ -1,12 +1,5 @@ -use base64::Engine; use bitwarden_api_api::models::{ AdminAuthRequestUpdateRequestModel, OrganizationUserResetPasswordDetailsResponseModel, - PendingOrganizationAuthRequestResponseModel, - PendingOrganizationAuthRequestResponseModelListResponseModel, -}; -use rsa::{ - pkcs8::{der::Decode, DecodePrivateKey, SubjectPublicKeyInfo}, - RsaPublicKey, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -14,40 +7,47 @@ use uuid::Uuid; use crate::{ client::Client, - crypto::{encrypt_rsa, Decryptable, EncString}, - error::{CryptoError, Error, Result}, - util::BASE64_ENGINE, + crypto::{encrypt_rsa, private_key_from_bytes, public_key_from_b64, Decryptable, EncString}, + error::{Error, Result}, }; +use super::{list_pending_requests, PendingAuthRequestResponse}; + +// TODO: what identifier should this take? e.g. org_user_id, request_id, etc +// using org_user_id for now #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AuthApproveRequest { - /// ID of the auth request to approve - pub request_id: Uuid, pub organization_user_id: Uuid, pub organization_id: Uuid, - pub device_public_key: String, } pub(crate) async fn approve_auth_request( client: &mut Client, input: &AuthApproveRequest, ) -> Result<()> { + let device_request = get_pending_request(input.organization_id, input.organization_user_id, client).await; + // Get user reset password details let reset_password_details = bitwarden_api_api::apis::organization_users_api::organizations_org_id_users_id_reset_password_details_get( &client.get_api_configurations().await.api, &input.organization_id.to_string(), - &input.request_id.to_string(), + &device_request.id.to_string(), ) .await?; - let encrypted_user_key = get_encrypted_user_key(&client, input, reset_password_details)?; + let encrypted_user_key = get_encrypted_user_key( + &client, + input.organization_id, + &device_request, + reset_password_details, + )?; bitwarden_api_api::apis::organization_auth_requests_api::organizations_org_id_auth_requests_request_id_post( &client.get_api_configurations().await.api, input.organization_id, - input.request_id, + device_request.id, Some(AdminAuthRequestUpdateRequestModel { encrypted_user_key: Some(encrypted_user_key.to_string()), request_approved: true @@ -58,9 +58,30 @@ pub(crate) async fn approve_auth_request( Ok(()) } +async fn get_pending_request(organization_id: Uuid, organization_user_id: Uuid, client: &mut Client) -> PendingAuthRequestResponse { + // hack: get all approval details and then find the one we want + // when we settle on an identifier then we should just give ourselves a better server API + // or do we require the caller to pass all this info in? + let all_device_requests = list_pending_requests( + client, + &super::PendingAuthRequestsRequest { + organization_id: organization_id, + }, + ) + .await; + + all_device_requests + .unwrap() + .data + .into_iter() + .find(|r| r.organization_user_id == organization_user_id) + .unwrap() // TODO: error handling +} + fn get_encrypted_user_key( client: &Client, - input: &AuthApproveRequest, + organization_id: Uuid, + input: &PendingAuthRequestResponse, reset_password_details: OrganizationUserResetPasswordDetailsResponseModel, ) -> Result { // Decrypt organization's encrypted private key with org key @@ -71,10 +92,10 @@ fn get_encrypted_user_key( .encrypted_private_key .ok_or(Error::MissingFields)? .parse::()? - .decrypt(enc, &Some(input.organization_id))? + .decrypt(enc, &Some(organization_id))? .into_bytes(); - rsa::RsaPrivateKey::from_pkcs8_der(&dec).map_err(|_| CryptoError::InvalidKey)? + private_key_from_bytes(&dec)? }; // Decrypt user key with org private key @@ -85,55 +106,8 @@ fn get_encrypted_user_key( let dec_user_key = user_key.decrypt_with_rsa_key(&org_private_key)?; // re-encrypt user key with device public key - let device_public_key_bytes = BASE64_ENGINE.decode(&input.device_public_key)?; - let device_public_key_info = SubjectPublicKeyInfo::from_der(&device_public_key_bytes).unwrap(); // TODO: error handling - let device_public_key = RsaPublicKey::try_from(device_public_key_info).unwrap(); // TODO: error handling - + let device_public_key = public_key_from_b64(&input.public_key_b64)?; let re_encrypted_user_key = encrypt_rsa(dec_user_key, &device_public_key)?; EncString::from_buffer(&re_encrypted_user_key) } - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct PendingAuthRequestsResponse { - pub data: Vec, -} - -impl PendingAuthRequestsResponse { - pub(crate) fn process_response( - response: PendingOrganizationAuthRequestResponseModelListResponseModel, - ) -> Result { - Ok(PendingAuthRequestsResponse { - data: response - .data - .unwrap_or_default() - .into_iter() - .map(|r| PendingAuthRequestResponse::process_response(r)) - .collect::>()?, - }) - } -} - -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct PendingAuthRequestResponse { - pub id: Uuid, - pub user_id: Uuid, - pub organization_user_id: Uuid, - pub email: String, - // TODO: map rest of fields -} - -impl PendingAuthRequestResponse { - pub(crate) fn process_response( - response: PendingOrganizationAuthRequestResponseModel, - ) -> Result { - Ok(PendingAuthRequestResponse { - id: response.id.ok_or(Error::MissingFields)?, - user_id: response.user_id.ok_or(Error::MissingFields)?, - organization_user_id: response.organization_user_id.ok_or(Error::MissingFields)?, - email: response.email.ok_or(Error::MissingFields)?, - }) - } -} diff --git a/crates/bw/src/main.rs b/crates/bw/src/main.rs index af49591c3..a200cbd49 100644 --- a/crates/bw/src/main.rs +++ b/crates/bw/src/main.rs @@ -1,6 +1,6 @@ use bitwarden::{ auth::RegisterRequest, client::client_settings::ClientSettings, tool::PasswordGeneratorRequest, - admin_console::auth_requests::{PendingAuthRequestsRequest, AuthApproveRequest} + admin_console::auth_requests::{PendingAuthRequestsRequest, AuthApproveRequest}, Client }; use bitwarden_cli::{install_color_eyre, text_prompt_when_none, Color}; use clap::{command, Args, CommandFactory, Parser, Subcommand}; @@ -101,7 +101,7 @@ enum GeneratorCommands { #[derive(Subcommand, Clone)] enum AdminConsoleCommands { ListDevices { organization_id: Uuid }, - ApproveDevice { id: Uuid } + ApproveDevice { organization_id: Uuid, organization_user_id: Uuid } } #[derive(Args, Clone)] @@ -134,6 +134,19 @@ async fn main() -> Result<()> { process_commands().await } +async fn hack_login() -> Client { + // hack login + let server = "https://vault.qa.bitwarden.pw"; + let settings = ClientSettings { + api_url: format!("{}/api", server), + identity_url: format!("{}/identity", server), + ..Default::default() + }; + let client = bitwarden::Client::new(Some(settings)); + + auth::api_key_login(client, None, None).await.unwrap() +} + async fn process_commands() -> Result<()> { let cli = Cli::parse(); @@ -226,19 +239,7 @@ async fn process_commands() -> Result<()> { }, Commands::AdminConsole { command } => match command { AdminConsoleCommands::ListDevices { organization_id } => { - - // hack login - let server = "https://vault.qa.bitwarden.pw"; - let settings = ClientSettings { - api_url: format!("{}/api", server), - identity_url: format!("{}/identity", server), - ..Default::default() - }; - let client = bitwarden::Client::new(Some(settings)); - - let mut client = auth::api_key_login(client, None, None).await?; - - // continue + let mut client = hack_login().await; let auth_requests = client .client_auth_requests() .list(&PendingAuthRequestsRequest { organization_id }) @@ -246,11 +247,13 @@ async fn process_commands() -> Result<()> { serialize_response(auth_requests.data, cli.output, false); }, - AdminConsoleCommands::ApproveDevice { id } => { - todo!() - // client - // .client_auth_requests() - // .approve(&AuthApproveRequest { id }) + AdminConsoleCommands::ApproveDevice { organization_id, organization_user_id } => { + let mut client = hack_login().await; + client + .client_auth_requests() + .approve(&AuthApproveRequest { organization_id, organization_user_id }) + .await + .unwrap(); // error handling? } } };