Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Folders state service using Typestate pattern #65

Open
wants to merge 12 commits into
base: PM-1724-state-management-v2
Choose a base branch
from
11 changes: 11 additions & 0 deletions crates/bitwarden-json/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::{
response::{Response, ResponseIntoString},
};

#[cfg(feature = "internal")]
use crate::command::FoldersCommand;

pub struct Client(bitwarden::Client);

impl Client {
Expand Down Expand Up @@ -72,6 +75,14 @@ impl Client {
ProjectsCommand::Update(req) => self.0.projects().update(&req).await.into_string(),
ProjectsCommand::Delete(req) => self.0.projects().delete(req).await.into_string(),
},

#[cfg(feature = "internal")]
Command::Folders(cmd) => match cmd {
FoldersCommand::Create(req) => self.0.folders().create(req).await.into_string(),
FoldersCommand::List(_) => self.0.folders().list().await.into_string(),
FoldersCommand::Update(req) => self.0.folders().update(req).await.into_string(),
FoldersCommand::Delete(req) => self.0.folders().delete(req).await.into_string(),
},
}
}

Expand Down
37 changes: 36 additions & 1 deletion crates/bitwarden-json/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use bitwarden::{
#[cfg(feature = "internal")]
use bitwarden::{
auth::request::{ApiKeyLoginRequest, PasswordLoginRequest, SessionLoginRequest},
platform::{FingerprintRequest, SecretVerificationRequest, SyncRequest},
platform::{EmptyRequest, FingerprintRequest, SecretVerificationRequest, SyncRequest},
vault::folders::{FolderCreateRequest, FolderDeleteRequest, FolderUpdateRequest},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -79,6 +80,9 @@ pub enum Command {

Secrets(SecretsCommand),
Projects(ProjectsCommand),

#[cfg(feature = "internal")]
Folders(FoldersCommand),
}

#[derive(Serialize, Deserialize, JsonSchema, Debug)]
Expand Down Expand Up @@ -168,3 +172,34 @@ pub enum ProjectsCommand {
///
Delete(ProjectsDeleteRequest),
}

#[cfg(feature = "internal")]
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub enum FoldersCommand {
/// > Requires Authentication
/// > Requires an unlocked vault
/// Creates a new folder with the provided data
///
Create(FolderCreateRequest),

/// > Requires Authentication
/// > Requires an unlocked vault and calling Sync at least once
/// Lists all folders in the vault
///
/// Returns: [FoldersResponse](bitwarden::vault::folders::FoldersResponse)
///
List(EmptyRequest),

/// > Requires Authentication
/// > Requires an unlocked vault
/// Updates an existing folder with the provided data given its ID
///
Update(FolderUpdateRequest),

/// > Requires Authentication
/// > Requires an unlocked vault
/// Deletes the folder associated with the provided ID
///
Delete(FolderDeleteRequest),
}
1 change: 1 addition & 0 deletions crates/bitwarden/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub mod platform;
pub mod secrets_manager;
pub(crate) mod state;
mod util;
pub mod vault;
pub mod wordlist;

pub use client::Client;
Expand Down
7 changes: 6 additions & 1 deletion crates/bitwarden/src/state/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ impl State {
) -> Result<()> {
// Before we create the storage profile, keep a copy of the current temporary storage (tokens, kdf params, etc)

use crate::client::{keys::store_keys_from_sync, profile::store_profile_from_sync};
use crate::{
client::{keys::store_keys_from_sync, profile::store_profile_from_sync},
vault::folders::store_folders_from_sync,
};
let state = self.account.lock().await.get();

// Create the new account state, and load the temporary storage into it
Expand All @@ -60,6 +63,8 @@ impl State {
store_keys_from_sync(profile.as_ref(), self).await?;
store_profile_from_sync(profile.as_ref(), self).await?;

store_folders_from_sync(data.folders.unwrap_or_default(), self).await?;

Ok(())
}

Expand Down
37 changes: 37 additions & 0 deletions crates/bitwarden/src/vault/client_folders.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use super::folders::{
create_folder, delete_folder, get_folder, list_folders, update_folder, FolderCreateRequest,
FolderDeleteRequest, FolderGetRequest, FolderResponse, FolderUpdateRequest, FoldersResponse,
};
use crate::{error::Result, Client};

pub struct ClientFolders<'a> {
pub(crate) client: &'a mut crate::Client,
}

impl<'a> ClientFolders<'a> {
pub async fn create(&mut self, input: FolderCreateRequest) -> Result<()> {
create_folder(self.client, input).await
}

pub async fn get(&self, input: FolderGetRequest) -> Result<FolderResponse> {
get_folder(self.client, input).await
}

pub async fn list(&self) -> Result<FoldersResponse> {
list_folders(self.client).await
}

pub async fn update(&mut self, input: FolderUpdateRequest) -> Result<()> {
update_folder(self.client, input).await
}

pub async fn delete(&mut self, input: FolderDeleteRequest) -> Result<()> {
delete_folder(self.client, input).await
}
}

impl<'a> Client {
pub fn folders(&'a mut self) -> ClientFolders<'a> {
ClientFolders { client: self }
}
}
37 changes: 37 additions & 0 deletions crates/bitwarden/src/vault/folders/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::FolderToSave;
use crate::{
client::encryption_settings::EncryptionSettings,
crypto::Encryptable,
error::{Error, Result},
Client,
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FolderCreateRequest {
/// Encrypted folder name
pub name: String,
}

impl Encryptable<FolderToSave> for FolderCreateRequest {
fn encrypt(self, enc: &EncryptionSettings, _: &Option<Uuid>) -> Result<FolderToSave> {
Ok(FolderToSave {
id: None,
name: enc.encrypt(&self.name.as_bytes(), &None)?,
})
}
}

pub(crate) async fn create_folder(client: &mut Client, input: FolderCreateRequest) -> Result<()> {
let enc = client
.get_encryption_settings()
.as_ref()
.ok_or(Error::VaultLocked)?;

input.encrypt(enc, &None)?.save_to_server(client).await?;
Ok(())
}
24 changes: 24 additions & 0 deletions crates/bitwarden/src/vault/folders/delete.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::folder::FolderToDelete;
use crate::{error::Result, Client};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct FolderDeleteRequest {
/// ID of the folder to delete
pub id: Uuid,
}

impl From<FolderDeleteRequest> for FolderToDelete {
fn from(input: FolderDeleteRequest) -> Self {
Self { id: input.id }
}
}

pub(crate) async fn delete_folder(client: &mut Client, input: FolderDeleteRequest) -> Result<()> {
let input: FolderToDelete = input.into();
Ok(input.delete_from_server(client).await?)
}
Loading